1

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()

  • If you are interested in segfault, then your question is a duplicate of https://stackoverflow.com/questions/47479323/what-stops-me-from-reading-writing-further-than-the-size-of-a-shared-memory-sy/47479378#47479378 –  Nov 25 '17 at 22:52
  • `reserve` affects capacity but doesn't change the size of the vector, try `resize`. – Bo Persson Nov 25 '17 at 22:52
  • but why then temp[8] doesn't give seg fault or a throw of `out_of_range`? – John Quicksilver Nov 25 '17 at 22:53
  • 2
    It is undefined behavior, *anything* can happen. Try `temp.at(8)` if you want an exception. – Bo Persson Nov 25 '17 at 22:54
  • 3
    @JohnQuicksilver -- C++ does not work this way, where if you make a mistake, a segfault will appear. You make a mistake like this, anything can happen. And if you want `out_of_range`, use `vector::at()`. – PaulMcKenzie Nov 25 '17 at 22:55
  • @JohnQuicksilver, Forcing that behaviour would have a speed cost for all of the programs out there that guarantee they don't go out of bounds. – chris Nov 25 '17 at 22:58
  • To add to the previous comments: C++ is about paying only for the additional checks that one wants. If `vector` would always do bounds checking, you would have to pay for it in code even though you can guarantee that you will never go out of bounds. Therefore C++ makes you opt-in (via `at` instead of `operator[]`) to the additional costly check. – Martin Ueding Nov 25 '17 at 23:03
  • 1
    @JohnQuicksilver -- Also, if you read any good C++ reference, if the function description doesn't say "this will throw an exception when...", then there is no guarantee what will happen when you make a mistake. If you [read the documentation on the bracket operator](http://en.cppreference.com/w/cpp/container/vector/operator_at), you don't see any mention of an exception possibly being thrown. However if you read [the documentation for at()](http://en.cppreference.com/w/cpp/container/vector/at), you clearly see that `std::out_of_range` is thrown on error. – PaulMcKenzie Nov 25 '17 at 23:07

6 Answers6

4

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.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62
2

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
Martin Ueding
  • 8,245
  • 6
  • 46
  • 92
  • 1
    This is a very impressive flag. Now I know how my professor identify errors in my projects and the reason for bad scores – John Quicksilver Nov 25 '17 at 23:10
  • Your professor might also use `valgrind`. It is a tool for virtually the same purpose, just that it uses binary instrumentation with an already compiled program whereas the sanitizer is a compiler feature. – Martin Ueding Nov 27 '17 at 09:52
1

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).

Peter
  • 35,646
  • 4
  • 32
  • 74
0

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.

Arash
  • 2,114
  • 11
  • 16
  • 1
    Even if `reserve` only reserves 8 elements, you still might get no segmentation fault when doing `temp[8]` because the memory that this corresponds to also belongs to your program. If the implementation of `reserve` always reserves a bit more memory, then it will never give a segmentation fault, but the behavior is still undefined. – Martin Ueding Nov 25 '17 at 22:59
  • @MartinUeding, I totally agree. – Arash Nov 25 '17 at 23:00
0

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

Eljay
  • 4,648
  • 3
  • 16
  • 27
0

at() has a in bounds check ,while [] does not do it , but at() is slower a littl bit and you could use assert with operator [] to get a very good result ....

reserv is saving extra space for Vector thus changing capacity ... resize is somthing totaly diffrent ....