当前位置:  开发笔记 > 编程语言 > 正文

C++程序员应该知道的所有常见的未定义行为是什么?

如何解决《C++程序员应该知道的所有常见的未定义行为是什么?》经验,为你挑选了6个好方法。

C++程序员应该知道的所有常见的未定义行为是什么?

说,像:

a[i] = i++;



1> Diomidis Spi..:

指针

取消引用NULL指针

取消引用由大小为零的"新"分配返回的指针

使用指向生命周期已结束的对象的指针(例如,堆栈已分配的对象或已删除的对象)

取消引用尚未明确初始化的指针

执行指针运算,在数组的边界(上方或下方)之外产生结果.

在超出数组末尾的位置取消引用指针.

将指针转换为不兼容类型的对象

使用memcpy复制重叠的缓冲区.

缓冲区溢出

以负数或超出该对象大小的偏移量读取或写入对象或数组(堆栈/堆溢出)

整数溢出

有符号整数溢出

评估未在数学上定义的表达式

左移值为负数(向右移位负值是实现定义的)

将值移动大于或等于数字中的位数(例如int64_t i = 1; i <<= 72,未定义)

类型,演员和Const

将数值转换为无法由目标类型表示的值(直接或通过static_cast)

在明确分配之前使用自动变量(例如int i; i++; cout << i;)

使用比其他类型的任何对象的值volatile或者sig_atomic_t在收到的信号的

尝试在其生命周期内修改字符串文字或任何其他const对象

在预处理期间将窄字符串与宽字符串文字连接起来

功能和模板

不从值返回函数返回值(直接或通过从try块中流出)

同一实体的多个不同定义(类,模板,枚举,内联函数,静态成员函数等)

模板实例化中的无限递归

使用不同的参数调用函数或链接到函数定义为使用的参数和链接.

OOP

具有静态存储持续时间的对象的级联破坏

分配给部分重叠的对象的结果

在其静态对象的初始化期间递归地重新进入函数

从构造函数或析构函数中对对象的纯虚函数进行虚函数调用

指未经建造或已被破坏的物体的非静止构件

源文件和预处理

非空源文件,不以换行符结尾,或以反斜杠结尾(在C++ 11之前)

反斜杠后跟一个字符,该字符不是字符或字符串常量中指定的转义码的一部分(这是在C++ 11中实现定义的).

超出实现限制(嵌套块的数量,程序中的函数数量,可用的堆栈空间......)

预处理器数值,不能用a表示 long int

类似函数的宏定义左侧的预处理指令

#if表达式中动态生成已定义的标记

待分类

在销毁具有静态存储持续时间的程序期间调用exit



2> Martin York..:

评估函数参数的顺序是未指定的行为.(这不会使您的程序崩溃,爆炸或订购披萨......与未定义的行为不同.)

唯一的要求是在调用函数之前必须完全评估所有参数.


这个:

// The simple obvious one.
callFunc(getA(),getB());

可以相当于:

int a = getA();
int b = getB();
callFunc(a,b);

或这个:

int b = getB();
int a = getA();
callFunc(a,b);

它可以是; 这取决于编译器.结果可能很重要,具体取决于副作用.


订单未指定,未定义.
那非常讨厌.
@Rob:我会和你争论这里意义的变化,但我知道标准委员会对这两个词的确切定义非常挑剔.所以我只是改变它:-)
我很幸运.当我在大学时,我被一个教授看了一眼,并在大约5秒内告诉我我的问题.不知道我会浪费多少时间来浪费调试.

3> Martin York..:

编译器可以自由地重新排序表达式的求值部分(假设含义不变).

从原来的问题:

a[i] = i++;

// This expression has three parts:
(a) a[i]
(b) i++
(c) Assign (b) to (a)

// (c) is guaranteed to happen after (a) and (b)
// But (a) and (b) can be done in either order.
// See n2521 Section 5.17
// (b) increments i but returns the original value.
// See n2521 Section 5.2.6
// Thus this expression can be written as:

int rhs  = i++;
int lhs& = a[i];
lhs = rhs;

// or
int lhs& = a[i];
int rhs  = i++;
lhs = rhs;

双重检查锁定.一个容易犯的错误.

A* a = new A("plop");

// Looks simple enough.
// But this can be split into three parts.
(a) allocate Memory
(b) Call constructor
(c) Assign value to 'a'

// No problem here:
// The compiler is allowed to do this:
(a) allocate Memory
(c) Assign value to 'a'
(b) Call constructor.
// This is because the whole thing is between two sequence points.

// So what is the big deal.
// Simple Double checked lock. (I know there are many other problems with this).
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        a = new A("Plop");  // (Point A).
    }
}
a->doStuff();

// Think of this situation.
// Thread 1: Reaches point A. Executes (a)(c)
// Thread 1: Is about to do (b) and gets unscheduled.
// Thread 2: Reaches point B. It can now skip the if block
//           Remember (c) has been done thus 'a' is not NULL.
//           But the memory has not been initialized.
//           Thread 2 now executes doStuff() on an uninitialized variable.

// The solution to this problem is to move the assignment of 'a'
// To the other side of the sequence point.
if (a == null) // (Point B)
{
    Lock   lock(mutex);
    if (a == null)
    {
        A* tmp = new A("Plop");  // (Point A).
        a = tmp;
    }
}
a->doStuff();

// Of course there are still other problems because of C++ support for
// threads. But hopefully these are addresses in the next standard.



4> yesraaj..:

剥离后const使用const_cast<>以下方式分配常量:

const int i = 10; 
int *p =  const_cast( &i );
*p = 1234; //Undefined



5> Daniel Earwi..:

我最喜欢的是"模板实例化中的无限递归"因为我相信它是唯一一个在编译时发生未定义行为的人.


不适合int的预处理器常量也是编译时.

6> Constantin..:

除了未定义的行为,还有同样讨厌的实现定义的行为.

当程序执行标准未指定结果的事情时,会发生未定义的行为.

实现定义的行为是程序的一个动作,其结果不是由标准定义的,而是需要实现的文档.一个例子是"多字节字符文字",来自Stack Overflow问题是否有一个C编译器无法编译它?.

实现定义的行为只会在您开始移植时咬你(但升级到新版本的编译器也在移植!)

推荐阅读
小色米虫_524
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有