簡體   English   中英

std::vector 中的保留如何工作 + 使用 [] 訪問向量

[英]How reserve in std::vector works + Accessing vector with []

為什么vector[n] = val在保留空向量之前不會給出分段錯誤或更改向量數據。 檢查這個例子:

#include <iostream>
#include <vector>
int main()
{

    std::vector<int> temp;
    temp.reserve(8);

    temp[0] = 1;

    temp[3] = 3; //why no attribution???
    temp[7] = 1;
    temp[8] = 3; //why no segmentation fault???

    std::cout << temp.size();
    for(auto&a: temp){ //because the attribution didn't work, no loop needed
        std::cout << a;
    }


    return 0;   

}

另外,為什么操作符 [] 不會拋出 'out_of_range',因為如果使用它而不是方法.at()

你有兩種不同的誤解。

  1. 你混淆了reserveresize
  2. 當您使用非法元素索引時,您預計會發生崩潰。

就目前而言,您的程序具有未定義的行為,因為temp以空向量temp.reserve(8); 不會改變它在外界看來的大小,而只會改變它的內部內存使用情況,並且temp[0]超出范圍。

未定義的行為意味着 C++ 語言不會說明會發生什么。 您的程序可能會也可能不會崩潰。

在幕后可能發生的事情是, reserve不僅為 8 個元素保留空間,而且為更多元素保留空間,因此內存訪問不會導致不好的事情發生。 您的 C++ 工具集可能根據三個指針實現std::vectorbeginlogical endphysical end 您的reserve呼叫增加了物理端,但不影響邏輯端 因此邏輯 end仍然等於begin ,所以size保持為零。

我們鼓勵您使用調試器來確定該假設是否在您的情況下實際成立。

順便說一句:如果您使用resize而不是reserve ,則越界訪問和未定義的行為將發生在temp[8] = 3; .

另外,為什么operator []不會拋出out_of_range ,因為如果使用它來代替方法.at()

因為不需要這樣做。 如果您想要有保證的異常,請使用at() 但是,您通常應該更喜歡operator[]並且不要讓越界錯誤發生。

您通過訪問不應該訪問的內存來創建未定義的行為。 不幸的是,內存仍然分配給您的程序,因此操作系統不會終止您的程序。

要查看您做錯了什么,請像這樣編譯您的程序:

g++ test.cpp -fsanitize=address

當你現在運行它時,它會很清楚地告訴你你正在做一些你不應該做的事情:

=================================================================                                                      
==17401==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x603000000030 at pc 0x000000401208 bp 0x7ffee64f71f0 sp 0x7ffee64f71e0                                                                                                     
WRITE of size 4 at 0x603000000030 thread T0                                                                            
    #0 0x401207 in main /home/mu/test.cpp:13                                                                           
    #1 0x7fbcba273889 in __libc_start_main (/lib64/libc.so.6+0x20889)                                                  
    #2 0x400f49 in _start (/home/mu/a.out+0x400f49)                                                                    

0x603000000030 is located 0 bytes to the right of 32-byte region [0x603000000010,0x603000000030)                       
allocated by thread T0 here:                                                                                           
    #0 0x7fbcbafbe158 in operator new(unsigned long) (/lib64/libasan.so.4+0xe0158)                                     
    #1 0x4022d6 in __gnu_cxx::new_allocator<int>::allocate(unsigned long, void const*) /usr/include/c++/7/ext/new_allocator.h:111                                                                                                             
    #2 0x40222d in std::allocator_traits<std::allocator<int> >::allocate(std::allocator<int>&, unsigned long) /usr/include/c++/7/bits/alloc_traits.h:436                                                                                      
    #3 0x402153 in std::_Vector_base<int, std::allocator<int> >::_M_allocate(unsigned long) /usr/include/c++/7/bits/stl_vector.h:172                                                                                                          
    #4 0x401ebb in int* std::vector<int, std::allocator<int> >::_M_allocate_and_copy<std::move_iterator<int*> >(unsigned long, std::move_iterator<int*>, std::move_iterator<int*>) /usr/include/c++/7/bits/stl_vector.h:1260                  
    #5 0x401671 in std::vector<int, std::allocator<int> >::reserve(unsigned long) /usr/include/c++/7/bits/vector.tcc:73
    #6 0x4010c9 in main /home/mu/test.cpp:7                                                                            
    #7 0x7fbcba273889 in __libc_start_main (/lib64/libc.so.6+0x20889)                                                  

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/mu/test.cpp:13 in main
Shadow bytes around the buggy address:
  0x0c067fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c067fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c067fff8000: fa fa 00 00 00 00[fa]fa fa fa fa fa fa fa fa fa
  0x0c067fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c067fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==17401==ABORTING

你的程序的行為是未定義的。 時期。

分段違規只是未定義行為的一種可能后果。 另一個可能的后果似乎是行為“正確”,但是您可以定義該概念。

實際上,段沖突是由 unix 主機系統檢測到您的程序訪問它不應該訪問的內存,並向您的程序發送信號SIGSEGV導致的,這反過來導致您的程序終止。 操作系統的檢測不是萬無一失的,因此它可能無法檢測到程序的所有實例越界 - 例如,如果修改向量的不存在元素會修改程序分配的某些內存區域另一個變量。

std::vector s operator[]()未指定為拋出異常,因此temp[n]其中n超出范圍0temp.size()-1不需要拋出異常。 它將具有未定義的行為。 temp.at()將拋出異常,給出范圍0temp.size()-1之外的值,但operator[]不需要。

調用temp.reserve(8)不會影響temp.size() temp.reserve()影響temp.capacity() ,唯一的限制是temp.capacity() >= temp.size() 由於在您的示例中, temp.size()為零(由默認構造函數實現),因此不能保證temp[n]可以訪問保留的內存,如果n介於07之間。 根據標准,該行為仍未定義。 它可能看起來“有效”,也可能無效。

temp.capacity() > temp.size()std::vector::reserve()一種可能實現是將額外的內存標記為不可訪問(從temp.size()temp.capacity() - 1所有元素) 並在n超出范圍時在每次使用temp[n]時觸發代碼中的錯誤條件。 這樣的實現需要temp.resize() (以及影響向量大小的其他操作)來標記0temp.size() - 1之間可訪問的元素。 沒有要求實現這樣做 - 因此您的代碼行為如您所見是允許的。 然而,同樣,沒有什么可以阻止實現我所描述的 - 因此在您的代碼中temp.reserve()之后的任何語句失敗也是允許的。

您的測試似乎表明訪問元素高達temp.capacity() - 可能更多 - 將“工作”。 在您測試時,這是特定於您的編譯器和標准庫的。 標准不保證它,並且您的代碼將來很容易被破壞(例如,當編譯器或標准庫更新到更新版本時)。

http://www.cplusplus.com/reference/vector/vector/reserve/

如果 n 大於當前向量容量,則該函數會導致容器重新分配其存儲,將其容量增加到 n (或更大)

順便說一句,您無法預測未定義的行為。 在庫的更新版本中,可能會發生崩潰。

使用temp.at(0) = 1; 以便更好地檢測問題。

at() 有一個邊界檢查,而 [] 不這樣做,但 at() 稍微慢一點,你可以使用帶有運算符 [] 的斷言來獲得非常好的結果......

reserv 為 Vector 節省了額外的空間,從而改變了容量……調整大小是完全不同的……

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM