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

9 浏览
0 Comments

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

C++程序员应该了解的所有常见未定义行为有哪些?

比如:

a[i] = i++;

0
0 Comments

C++程序员应该了解的常见未定义行为有哪些?

编译器可以自由地重新排序表达式的评估部分(假设意义不变)。

双重检查锁。

一个常见的错误。

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

这个看起来很简单。

但是它可以分为三个部分。

(a) 分配内存

(b) 调用构造函数

(c) 将值分配给'a'

没有任何问题:

编译器可以这样做:

(a) 分配内存

(c) 将值分配给'a'

(b) 调用构造函数。

这是因为整个过程在两个序列点之间。

那么有什么大不了的。

简单的双重检查锁(我知道这里还有很多其他问题)。

if (a == null) // (B点)

{

Lock lock(mutex);

if (a == null)

{

a = new A("Plop"); // (A点)

}

}

a->doStuff();

想象这种情况。

线程1: 到达A点。执行(a)(c)

线程1: 即将执行(b)然后被取消调度。

线程2: 到达B点。它现在可以跳过if块

记住(c)已经完成,所以'a'不是NULL。

但是内存尚未初始化。

线程2现在在一个未初始化的变量上执行doStuff()。

解决这个问题的方法是将'a'的赋值移到序列点的另一侧。

if (a == null) // (B点)

{

Lock lock(mutex);

if (a == null)

{

A* tmp = new A("Plop"); // (A点)

a = tmp;

}

}

a->doStuff();

当然,由于C++对线程的支持,仍然存在其他问题。但希望这些问题在下一个标准中得到解决。

序列点是什么意思?

http://en.wikipedia.org/wiki/Sequence_point

哦...这很恶劣,尤其是因为我看到过在Java中推荐使用这个确切的结构。

注意,有些编译器确实定义了这种情况的行为。例如,在VC++ 2005+中,如果'a'是volatile的,则会设置所需的内存屏障,以防止指令重新排序,从而使双重检查锁起作用。

Martin York: // (c) is guaranteed to happen after (a) and (b) 是吗?尽管在那个特定的示例中,只有当'i'是映射到硬件寄存器的volatile变量,并且a[i]('i'的旧值)是其别名时,才会发生这种情况,但是否有保证增量将在“i=i++”赋值发生之前完成?

Yes,它是有保证的。在评估“=”之前,必须评估“=”的两侧。

York: 表达式“i=i++;”通常被认为是C中未定义行为的一个例子(我知道在一些其他语言(如Java)中,行为是定义的)。如果增量的副作用不能保证在“i=i++;”赋值发生时已经完成,为什么它们保证在“a[i]=i++;”发生时已经完成?

OK,我明白你的意思了。你是正确的,运算符++(对i)的效果不能保证到达那一点已经完成。

a[i] = i++;无论如何都是未定义行为。编译器可以做更多的事情,而不仅仅是重新排序它,它可以忽略它或者戴上鼻子。i在RHS上被修改,在LHS上读取的目的不仅仅是确定要写入的值。游戏结束(除非i是一个具有operator++重载的类类型,那么在该重载内部进行的任何对象修改都会被安全地包装在序列点中,并且只是未指定的顺序而不是UB)。

绝对正确。我只是试图解释为什么它需要是未定义的(以及一些可能的实现解释)。

不确定是否同意这样解释为什么它必须是未定义的。在其他情况下,操作顺序是不确定的,而不会引发UB。例如,在我提到的情况下,i是一个模拟整数的类类型,并具有适当的operator++(int)和operator int(),你的两个选项都仍然可能,但没有UB。也就是说,也许在说“未定义行为”时,提问者的意思是任何行为都不完全确定的情况,而不是标准对UB的定义。

你的双重检查锁示例是恶意的。 ಥ_ಥ

嗨,我想知道第二个版本的单例实现还有什么其他问题。看起来它是完美的。

如果你想用指针创建一个单例,你是错误的。参见C++ Singleton设计模式。

是的,我也在"Effective C++"的第4项中读到过。这个在C++0x之后是线程安全的。但为什么不能使用指针?如果不使用指针,我只是返回指针的解引用,因为单例应该在整个进程的生命周期中存在,我不需要使用显式指针来销毁它。

特别是,我想知道上面的代码是否被认为是DCLP的一个相当好的实现。

不,它在C++03中不好。在C++中,有关线程和双重检查锁的问题使得无法正确执行;Sutter有一篇著名的论文。我相信在C++11中已经修复了这个问题,但你需要使用此处未记录的新功能。

这是它:C++ and The Perils of Double-Checked Locking 实际上是由Meyers和Alexandrescu撰写的。

0
0 Comments

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

在C++中,函数参数的求值顺序是未指定行为。这不会导致程序崩溃、爆炸或订披萨,与未定义行为不同。

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

这样写:

// 简单明了的写法。
callFunc(getA(),getB());

可以等同于这样写:

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

或者这样写:

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

它可以是任意一种方式;这由编译器决定。结果可能会有影响,这取决于副作用。

顺序是未指定的,不是未定义的。

我讨厌这一点 🙂 有一次我因为追踪这种情况而浪费了一整天的工作时间...不过幸运的是,我吸取了教训,再也没有犯过同样的错误。

:我想争论一下这里的意义变化,但我知道标准委员会对这两个词的确切定义非常挑剔。所以我就改了它 🙂

我在这件事上很幸运。我在大学时曾被它咬过一次,当时我的教授一眼就看出了问题,用了大约5秒钟告诉我。要不然,我要浪费多少时间来调试呢?

0
0 Comments

C++是一种功能强大的编程语言,但是在编写C++代码时,程序员需要了解并避免常见的未定义行为。未定义行为指的是在程序中使用了一些操作或语法,但是C++标准没有对其行为进行明确定义的情况。这些未定义行为可能导致程序崩溃、产生不可预测的结果或安全漏洞。

以下是常见的C++程序员应该了解的未定义行为:

指针:

- 解引用一个空指针

- 解引用由"new"分配的大小为零的指针

- 使用指向已经结束生命周期的对象的指针

- 解引用尚未被确定初始化的指针

- 执行指针算术运算得到超出数组边界的结果

- 解引用超出数组末尾的指针

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

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

缓冲区溢出:

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

整数溢出:

- 有符号整数溢出

- 计算数学上未定义的表达式

- 将值左移负数位(右移负数位是实现定义的)

- 将值左移或右移超过数的位数

类型、转换和常量:

- 将数值转换为目标类型无法表示的值

- 在自动变量被确定赋值之前使用它

- 在接收到信号时使用任何类型的对象的值(除了volatilesig_atomic_t类型)

- 在其生命周期内尝试修改字符串字面量或其他const对象

- 在预处理期间连接窄字符串和宽字符串字面量

函数和模板:

- 从有返回值的函数中没有返回值

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

- 模板的实例化中出现无限递归

- 以不同的参数或链接方式调用函数

面向对象编程:

- 静态存储期对象的级联销毁

- 对部分重叠对象进行赋值的结果

- 在静态对象初始化过程中递归重新进入函数

- 在构造函数或析构函数中调用虚函数的纯虚函数

- 引用尚未构造或已经析构的非静态成员

源文件和预处理:

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

- 反斜杠后跟一个不是字符或字符串常量中指定转义代码的字符(C++11中是实现定义的)

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

- 预处理器数值无法由long int表示

- 函数样式宏定义的左侧有预处理指令

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

还有一些未定义行为没有被分类,比如在程序静态存储期结束时调用exit函数。

为什么C++将x / 0定义为未定义行为,即使IEEE 754已经覆盖了NaN(x / 0)和Infinity(0 / 0)?这是因为并非所有的浮点单元都与IEEE 754兼容。如果C++要求IEEE 754兼容性,编译器将需要测试和处理RHS为零的情况,这会增加额外的开销。通过将行为定义为未定义,编译器可以避免这种开销,因为它可以说“如果你使用的是非IEEE 754浮点单元,你将不会得到IEEE 754浮点单元的行为”。

文章中提供了C99标准附录J.2中的一份未定义行为列表,但需要根据C++标准进行调整。这个列表为程序员提供了一个起点。

了解并避免这些常见的未定义行为对于编写稳定、可靠和安全的C++代码至关重要。程序员应该熟悉C++标准,并在编写代码时遵循最佳实践,以避免未定义行为的出现。

0