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

int main() {

int& r = getX(); std::cout << r << "\n";

}

In this example, the local variable x goes out of scope when getX returns. (Note that lifetime extension cannot extend the lifetime of a local variable past the scope of the block in which it is defined.) Therefore r is a dangling reference. This program has undefined behavior, although it may appear to work and print 42 in some cases.

Section 104.9: Integer division by zero

int x = 5 / 0; // Undefined behavior

Division by 0 is mathematically undefined, and as such it makes sense that this is undefined behavior.

However:

float x = 5.0f / 0.0f; // x is +infinity

Most implementation implement IEEE-754, which defines floating point division by zero to return NaN (if numerator is 0.0f), infinity (if numerator is positive) or -infinity (if numerator is negative).

Section 104.10: Shifting by an invalid number of positions

For the built-in shift operator, the right operand must be nonnegative and strictly less than the bit width of the promoted left operand. Otherwise, the behavior is undefined.

const int a = 42;

const int b = a << -1; // UB const int c = a << 0; // ok

const int d = a << 32; // UB if int is 32 bits or less const int e = a >> 32; // also UB if int is 32 bits or less const signed char f = 'x';

const int g = f << 10; // ok even if signed char is 10 bits or less; // int must be at least 16 bits

Section 104.11: Incorrect pairing of memory allocation and deallocation

An object can only be deallocated by delete if it was allocated by new and is not an array. If the argument to delete was not returned by new or is an array, the behavior is undefined.

An object can only be deallocated by delete[] if it was allocated by new and is an array. If the argument to delete[] was not returned by new or is not an array, the behavior is undefined.

If the argument to free was not returned by malloc, the behavior is undefined.

int* p1 = new int;

 

delete p1;

// correct

// delete[] p1;

// undefined

// free(p1);

//

undefined

int* p2 = new int[10];

delete[] p2;

// correct

// delete p2;

// undefined

// free(p2);

// undefined

GoalKicker.com – C++ Notes for Professionals

526

int* p3 = static_cast<int*>(malloc(sizeof(int)));

free(p3);

// correct

// delete p3;

// undefined

// delete[] p3;

// undefined

Such issues can be avoided by completely avoiding malloc and free in C++ programs, preferring the standard library smart pointers over raw new and delete, and preferring std::vector and std::string over raw new and delete[].

Section 104.12: Signed Integer Overflow

int x = INT_MAX + 1;

// x can be anything -> Undefined behavior

If during the evaluation of an expression, the result is not mathematically defined or not in the range of representable values for its type, the behavior is undefined.

(C++11 Standard paragraph 5/4)

This is one of the more nasty ones, as it usually yields reproducible, non-crashing behavior so developers may be tempted to rely heavily on the observed behavior.

On the other hand:

unsigned int x = UINT_MAX + 1;

// x is 0

is well defined since:

Unsigned integers, declared unsigned, shall obey the laws of arithmetic modulo 2^n where n is the number of bits in the value representation of that particular size of integer.

(C++11 Standard paragraph 3.9.1/4)

Sometimes compilers may exploit an undefined behavior and optimize

signed int x ; if(x > x + 1)

{

//do something

}

Here since a signed integer overflow is not defined, compiler is free to assume that it may never happen and hence it can optimize away the "if" block

Section 104.13: Multiple non-identical definitions (the One Definition Rule)

If a class, enum, inline function, template, or member of a template has external linkage and is defined in multiple translation units, all definitions must be identical or the behavior is undefined according to the One Definition Rule

GoalKicker.com – C++ Notes for Professionals

527

(ODR).

foo.h:

class Foo { public:

double x; private:

int y;

};

Foo get_foo();

foo.cpp:

#include "foo.h"

Foo get_foo() { /* implementation */ }

main.cpp:

// I want access to the private member, so I am going to replace Foo with my own type class Foo {

public: double x; int y;

};

Foo get_foo(); // declare this function ourselves since we aren't including foo.h int main() {

Foo foo = get_foo();

// do something with foo.y

}

The above program exhibits undefined behavior because it contains two definitions of the class ::Foo, which has external linkage, in di erent translation units, but the two definitions are not identical. Unlike redefinition of a class within the same translation unit, this problem is not required to be diagnosed by the compiler.

Section 104.14: Modifying a const object

Any attempt to modify a const object results in undefined behavior. This applies to const variables, members of const objects, and class members declared const. (However, a mutable member of a const object is not const.)

Such an attempt can be made through const_cast:

const int x = 123; const_cast<int&>(x) = 456; std::cout << x << '\n';

A compiler will usually inline the value of a const int object, so it's possible that this code compiles and prints 123. Compilers can also place const objects' values in read-only memory, so a segmentation fault may occur. In any case, the behavior is undefined and the program might do anything.

The following program conceals a far more subtle error:

#include <iostream>

class Foo* instance;

GoalKicker.com – C++ Notes for Professionals

528

class Foo { public:

int get_x() const { return m_x; } void set_x(int x) { m_x = x; }

private:

Foo(int x, Foo*& this_ref): m_x(x) { this_ref = this;

}

int m_x;

friend const Foo& getFoo();

};

const Foo& getFoo() {

static const Foo foo(123, instance); return foo;

}

void do_evil(int x) { instance->set_x(x);

}

int main() {

const Foo& foo = getFoo(); do_evil(456);

std::cout << foo.get_x() << '\n';

}

In this code, getFoo creates a singleton of type const Foo and its member m_x is initialized to 123. Then do_evil is

called and the value of foo.m_x is apparently changed to 456. What went wrong?

Despite its name, do_evil does nothing particularly evil; all it does is call a setter through a Foo*. But that pointer points to a const Foo object even though const_cast was not used. This pointer was obtained through Foo's constructor. A const object does not become const until its initialization is complete, so this has type Foo*, not const Foo*, within the constructor.

Therefore, undefined behavior occurs even though there are no obviously dangerous constructs in this program.

Section 104.15: Returning from a [[noreturn]] function

Version ≥ C++11

Example from the Standard, [dcl.attr.noreturn]:

[[ noreturn ]] void f() { throw "error"; // OK

}

[[ noreturn ]] void q(int i) { // behavior is undefined if called with an argument <= 0 if (i > 0)

throw "positive";

}

Section 104.16: Infinite template recursion

Example from the Standard, [temp.inst]/17:

template<class T> class X { X<T>* p; // OK

X<T*> a; // implicit generation of X<T> requires

// the implicit instantiation of X<T*> which requires

GoalKicker.com – C++ Notes for Professionals

529