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

无人久伴 提交于 2021-02-20 04:44:07

问题


Why vector[n] = val doesn't give segmentation fault or changes the vector data, right before reserving an empty vector. Check this example:

#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;   

}

Also, why the operator [] doesn't throw 'out_of_range', as the program would if it was used instead the method .at()


回答1:


You are creating undefined behavior by accessing memory that you should not. Unfortunately, the memory is still allocated to your program, therefore the operating system does not kill your program.

To see that you are doing something wrong, compile your program like so:

g++ test.cpp -fsanitize=address

When you now run it, it will tell you pretty clearly that you are doing something you should not:

=================================================================                                                      
==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



回答2:


You have two different misconceptions.

  1. You have confused reserve with resize.
  2. You expect a crash to happen when you use an illegal element index.

As it stands, your program has undefined behaviour because temp starts as an empty vector, temp.reserve(8); doesn't change its size as it appears to the outside world but only its internal memory usage, and temp[0] is out of bounds.

Undefined behaviour means that the C++ language doesn't say what will happen. Your program may or may not crash.

What probably happens behind the scenes is that reserve reserves space not only for 8 elements but for a bit more, so the memory access doesn't cause bad things to happen. Your C++ toolset probably implements std::vector in terms of three pointers: begin, logical end and physical end. Your reserve call increases the physical end but leaves the logical end alone. The logical end is thus still equal to begin, so size remains zero.

You are encouraged to use a debugger to find out whether this hypothesis actually holds in your situation or not.

By the way: If you used resize instead of reserve, then the out-of-bounds access and the undefined behaviour would happen at temp[8] = 3;.

Also, why the operator [] doesn't throw out_of_range, as the program would if it was used instead the method .at()

Because it is not required to do so. If you want a guaranteed exception, use at(). However, you should normally prefer operator[] and just not let an out-of-bounds error happen.




回答3:


The behaviour of your program is undefined. Period.

A segmentation violation is only one possible consequence of undefined behaviour. Another possible consequence is seeming to behave "correctly", however you might define that notion.

Practically, a segmentation violation results from a unix host system detecting your program accessing memory it shouldn't, and sending a signal SIGSEGV to your program, which in turn causes your program to terminate. The detection by the operating system is not fool-proof, so it may not detect all instances of your program going out of bounds - for example, if modifying a non-existent element of a vector modifies some area of memory allocated by your program for another variable.

A std::vectors operator[]() is not specified as throwing an exception, so temp[n] where n is outside the range 0 to temp.size()-1 is not required to throw an exception. It will have undefined behaviour. temp.at() will throw an exception given a value outside the range 0 to temp.size()-1, but operator[] will is not required to.

Calling temp.reserve(8) does not affect temp.size(). temp.reserve() affects temp.capacity(), and the only constraint is that temp.capacity() >= temp.size(). Since, in your example, temp.size() is zero (that is achieved by the default constructor) there is no guarantee that usage of temp[n] can access the reserved memory if n is between 0 and 7. The behaviour is still undefined according to the standard. It may seem to "work" or it may not.

When temp.capacity() > temp.size(), one possible implementation of std::vector::reserve() would be to mark additional memory inaccessible (all elements from temp.size() to temp.capacity() - 1) and trigger an error condition in your code for every usage of temp[n] when n is out of bounds. Such an implementation would require temp.resize() (and other operations that affect size of the vector) to mark elements accessible between 0 and temp.size() - 1. There is no requirement that an implementation do that - so your code behaving as you see is permitted. Equally, however, there is nothing preventing an implementation doing what I describe - so failing on any statement after temp.reserve() in your code is also permitted.

Your testing seems to indicate that accessing elements up to temp.capacity() - possibly even more - will "work". That is specific to your compiler and standard library, at the time you tested. It is not guaranteed by the standard, and your code could easily break in future (e.g. when the compiler or standard library are updated to a newer version).




回答4:


Use temp.at(0) = 1; for better problem detection.




回答5:


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

If n is greater than the current vector capacity, the function causes the container to reallocate its storage increasing its capacity to n (or greater).

BTW, you cannot predict an undefined behavior. In an updated version of libraries a crash might happen.



来源:https://stackoverflow.com/questions/47491286/how-reserve-in-stdvector-works-accessing-vector-with

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!