Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CPlusPlusNotesForProfessionals.pdf
Скачиваний:
47
Добавлен:
20.05.2023
Размер:
5.11 Mб
Скачать

Chapter 104: Undefined Behavior

What is undefined behavior (UB)? According to the ISO C++ Standard (§1.3.24, N4296), it is "behavior for which this International Standard imposes no requirements."

This means that when a program encounters UB, it is allowed to do whatever it wants. This often means a crash, but it may simply do nothing, make demons fly out of your nose, or even appear to work properly!

Needless to say, you should avoid writing code that invokes UB.

Section 104.1: Reading or writing through a null pointer

int *ptr = nullptr;

*ptr = 1; // Undefined behavior

This is undefined behavior, because a null pointer does not point to any valid object, so there is no object at *ptr to write to.

Although this most often causes a segmentation fault, it is undefined and anything can happen.

Section 104.2: Using an uninitialized local variable

int a;

std::cout << a; // Undefined behavior!

This results in undefined behavior, because a is uninitialised.

It is often, incorrectly, claimed that this is because the value is "indeterminate", or "whatever value was in that memory location before". However, it is the act of accessing the value of a in the above example that gives undefined behaviour. In practice, printing a "garbage value" is a common symptom in this case, but that is only one possible form of undefined behaviour.

Although highly unlikely in practice (since it is reliant on specific hardware support) the compiler could equally well electrocute the programmer when compiling the code sample above. With such a compiler and hardware support, such a response to undefined behaviour would markedly increase average (living) programmer understanding of the true meaning of undefined behaviour - which is that the standard places no constraint on the resultant behaviour.

Version ≥ C++14

Using an indeterminate value of unsigned char type does not produce undefined behavior if the value is used as:

the second or third operand of the ternary conditional operator;

the right operand of the built-in comma operator; the operand of a conversion to unsigned char;

the right operand of the assignment operator, if the left operand is also of type unsigned char; the initializer for an unsigned char object;

or if the value is discarded. In such cases, the indeterminate value simply propagates to the result of the expression, if applicable.

Note that a static variable is always zero-initialized (if possible):

GoalKicker.com – C++ Notes for Professionals

522

static int a;

std::cout << a; // Defined behavior, 'a' is 0

Section 104.3: Accessing an out-of-bounds index

It is undefined behavior to access an index that is out of bounds for an array (or standard library container for that matter, as they are all implemented using a raw array):

int array[] = {1, 2, 3, 4, 5}; array[5] = 0; // Undefined behavior

It is allowed to have a pointer pointing to the end of the array (in this case array + 5), you just can't dereference it, as it is not a valid element.

const int *end = array + 5; // Pointer to one past the last index for (int *p = array; p != end; ++p)

// Do something with `p`

In general, you're not allowed to create an out-of-bounds pointer. A pointer must point to an element within the array, or one past the end.

Section 104.4: Deleting a derived object via a pointer to a base class that doesn't have a virtual destructor

class base { };

class derived: public base { };

int main() {

base* p = new derived();

delete p; // The is undefined behavior!

}

In section [expr.delete] §5.3.5/3 the standard says that if delete is called on an object whose static type does not have a virtual destructor:

if the static type of the object to be deleted is di erent from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined.

This is the case regardless of the question whether the derived class added any data members to the base class.

Section 104.5: Extending the `std` or `posix` Namespace

The standard (17.6.4.2.1/1) generally forbids extending the std namespace:

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace std or to a namespace within namespace std unless otherwise specified.

The same goes for posix (17.6.4.2.2/1):

GoalKicker.com – C++ Notes for Professionals

523

The behavior of a C++ program is undefined if it adds declarations or definitions to namespace posix or to a namespace within namespace posix unless otherwise specified.

Consider the following:

#include <algorithm>

namespace std

{

int foo(){}

}

Nothing in the standard forbids algorithm (or one of the headers it includes) defining the same definition, and so this code would violate the One Definition Rule.

So, in general, this is forbidden. There are specific exceptions allowed, though. Perhaps most usefully, it is allowed to add specializations for user defined types. So, for example, suppose your code has

class foo

{

// Stuff

};

Then the following is fine

namespace std

{

template<> struct hash<foo>

{

public:

size_t operator()(const foo &f) const;

};

}

Section 104.6: Invalid pointer arithmetic

The following uses of pointer arithmetic cause undefined behavior:

Addition or subtraction of an integer, if the result does not belong to the same array object as the pointer operand. (Here, the element one past the end is considered to still belong to the array.)

int a[10];

 

 

 

 

int* p1 =

&a[5];

 

 

int* p2 =

p1

+ 4;

// ok; p2

points to a[9]

int* p3 =

p1

+ 5;

// ok; p2

points to one past the end of a

int* p4

=

p1

+ 6;

// UB

 

int* p5

=

p1

- 5;

// ok; p2

points to a[0]

int* p6

=

p1

- 6;

// UB

 

int* p7

=

p3

- 5;

// ok; p7

points to a[5]

 

 

 

 

 

 

Subtraction of two pointers if they do not both belong to the same array object. (Again, the element one past the end is considered to belong to the array.) The exception is that two null pointers may be subtracted, yielding 0.

GoalKicker.com – C++ Notes for Professionals

524

int a[10]; int b[10];

int *p1 = &a[8], *p2 = &a[3]; int d1 = p1 - p2; // yields 5

int *p3 = p1 + 2; // ok; p3 points to one past the end of a int d2 = p3 - p2; // yields 7

int *p4 = &b[0];

int d3 = p4 - p1; // UB

Subtraction of two pointers if the result overflows std::ptrdiff_t.

Any pointer arithmetic where either operand's pointee type does not match the dynamic type of the object pointed to (ignoring cv-qualification). According to the standard, "[in] particular, a pointer to a base class cannot be used for pointer arithmetic when the array contains objects of a derived class type."

struct Base { int

x; };

 

struct Derived : Base { int

y; };

Derived a[10];

 

 

Base* p1 =

&a[1];

 

// ok

Base* p2

=

p1 + 1;

 

// UB; p1 points to Derived

Base* p3

=

p1 - 1;

 

// likewise

Base* p4

=

&a[2];

 

// ok

auto p5 = p4 - p1;

 

// UB; p4 and p1 point to Derived

const Derived* p6

= &a[1];

 

const Derived* p7

= p6 + 1; // ok; cv-qualifiers don't matter

 

 

 

 

 

Section 104.7: No return statement for a function with a nonvoid return type

Omitting the return statement in a function which is has a return type that is not void is undefined behavior.

int function() {

// Missing return statement

}

int main() {

function(); //Undefined Behavior

}

Most modern day compilers emit a warning at compile time for this kind of undefined behavior.

Note: main is the only exception to the rule. If main doesn't have a return statement, the compiler automatically inserts return 0; for you, so it can be safely left out.

Section 104.8: Accessing a dangling reference

It is illegal to access a reference to an object that has gone out of scope or been otherwise destroyed. Such a reference is said to be dangling since it no longer refers to a valid object.

#include <iostream> int& getX() {

int x = 42; return x;

}

GoalKicker.com – C++ Notes for Professionals

525