Undefined behaviour: one read past the end
لم يُترجم بعد؛ يُعرض باللغة الإنجليزية
Some mistakes in C++ are not errors at all in the eyes of the language: the standard simply stops saying what happens. Follow an eight-line program whose loop reads one element past the end of an array. It compiles without a warning, nothing checks the read, and the result is undefined. Then see why optimisers make it worse, how AddressSanitizer catches it, and how to fix it.
5 أسئلة سريعة
اختر إجابة لتعرف السبب. تُحفَظ نتيجتك مع تقدّمك.
01int a[3] = {10, 20, 30}; Which indexes may you use with a?
An array of N elements has indexes 0 to N-1. a[3] names the spot just past the end, which is not part of a, and reading it is undefined behaviour.
02What does 'undefined behaviour' mean in C++?
Undefined means the standard says nothing at all: no promised error, no promised crash, not even the same result every time.
03The buggy program compiles with -Wall -Wextra and prints 60 on your machine. What can you conclude?
A clean build and a right-looking answer do not prove a program free of UB. The result can change with the compiler, the optimisation level or the machine.
04Why can turning on -O2 change what a program with UB does?
Optimisers reason from the rules of the language. A correct program never reads a[3], so an optimiser may conclude the loop ends before i reaches 3 and remove the end test.
05Which build is most likely to stop the program right at the bad read, naming the line?
AddressSanitizer puts poisoned red zones around a and checks every access. The read of a[3] stops the program with a stack-buffer-overflow report at sum.cpp:6.
المسرد
- Undefined behaviour (UB)
- What a program does after it breaks certain rules of C++, such as reading past the end of an array. The standard places no requirements on it.
- Array
- A fixed number of elements of one type stored side by side in memory, such as int a[3].
- Index
- The position of an element in an array, counted from 0. For N elements the valid indexes are 0 to N-1.
- Out of bounds
- An index outside 0 to N-1. Reading or writing there is undefined behaviour for a built-in array.
- Bounds check
- A test that an index is valid before it is used. Built-in arrays have none; std::array::at and std::vector::at do.
- Optimiser
- The part of the compiler that rewrites code to run faster, assuming the program has no undefined behaviour.
- AddressSanitizer (ASan)
- A GCC and Clang feature (-fsanitize=address) that checks memory accesses while the program runs and reports bad ones.
الملاحظات والمصادر
- The C++ standard calls these cases undefined behaviour and places no requirements on them. cppreference's examples include memory access outside an array's bounds, signed integer overflow, dereferencing a null pointer and reading an uninitialised variable; using an object after its lifetime has ended (for example after delete) is undefined too.
- The program compiles with no warnings under clang++ -std=c++20 -Wall -Wextra (checked by this lesson's tests). Some compilers can warn about some loops like this one when optimising, but they are not required to, and most UB cannot be seen at compile time.
- Compilers optimise on the assumption that a program has no UB. cppreference's page on undefined behaviour shows a loop that reads one element past an array being compiled to 'return true'; the GCC 4.8 release notes describe loops in real programs that stopped working as expected for the same reason.
- The figure does not show a value for a[3], because there is none to show: what a plain build prints is not defined and differs between compilers, settings and runs.
- AddressSanitizer comes with GCC and Clang (-fsanitize=address; add -g for line numbers). It surrounds objects with poisoned 'red zones' and checks every memory access, at a typical cost of about 2x run time. Microsoft's compiler supports /fsanitize=address. The report on the terminal is shortened; the real one also lists addresses and a memory map.
- An int is 4 bytes on the common desktop and phone platforms, which is why the report says READ of size 4; the C++ standard only requires at least 16 bits.
- cppreference.com: Undefined behavior
- cppreference.com: Array declaration
- cppreference.com: Member access operators (built-in subscript)
- cppreference.com: Range-based for loop
- cppreference.com: std::array::at
- cppreference.com: Lifetime
- C++ Core Guidelines (isocpp.org), SL.con.3: Avoid bounds errors; ES.20; ES.103
- Standard C++ Foundation (isocpp.org): Get Started!
- Clang documentation: AddressSanitizer
- GCC manual: Program Instrumentation Options (-fsanitize=address)
- GCC 4.8 release notes (aggressive loop bounds from language constraints)
اختبار يتاح بعد الخطوة الأخيرة.