考虑这个代码:
int i = 1;
int x = ++i + ++i;
Run Code Online (Sandbox Code Playgroud)
我们对编译器可能会为这段代码做些什么有一些猜测,假设它可以编译。
++i返回2,导致x=4.++i返回2,另一个返回3,结果为x=5。++i返回3,导致x=6.对我来说,第二个似乎最有可能。两个++运算符之一用 执行i = 1,i递增,并2返回结果。然后用++执行第二个运算符i = 2,i递增,并3返回结果。然后2和3相加得到5。
但是,我在 Visual Studio 中运行了这段代码,结果是6. 我试图更好地理解编译器,我想知道什么可能导致6. 我唯一的猜测是代码可以通过一些“内置”并发来执行。++调用了两个运算符,每个运算符i在另一个返回之前递增,然后它们都返回3。这与我对调用堆栈的理解相矛盾,需要加以解释。
C++ …
编译:
#include <iostream>
int main()
{
for (int i = 0; i < 4; ++i)
std::cout << i*1000000000 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
并gcc产生以下警告:
warning: iteration 3u invokes undefined behavior [-Waggressive-loop-optimizations]
std::cout << i*1000000000 << std::endl;
^
Run Code Online (Sandbox Code Playgroud)
我知道有一个有符号的整数溢出.
我不能得到的是为什么i价值被溢出操作打破了?
我已经阅读了为什么x86上的整数溢出与GCC导致无限循环的答案?,但我仍然不清楚为什么会发生这种情况 - 我认为"未定义"意味着"任何事情都可能发生",但这种特定行为的根本原因是什么?
编译: gcc (4.8)
GCC 6具有一个新的优化器功能:它假定this始终不为null并基于此进行优化.
值范围传播现在假定C++成员函数的this指针是非null.这消除了常见的空指针检查,但也打破了一些不符合规范的代码库(例如Qt-5,Chromium,KDevelop).作为临时解决方法,可以使用-fno-delete-null-pointer-checks.使用-fsanitize = undefined可以识别错误的代码.
更改文档清楚地将其称为危险,因为它打破了大量频繁使用的代码.
为什么这个新假设会破坏实用的C++代码?是否存在粗心或不知情的程序员依赖于这种特定的未定义行为的特定模式?我无法想象有人写作,if (this == NULL)因为那是不自然的.
以下代码进入GCC的无限循环:
#include <iostream>
using namespace std;
int main(){
int i = 0x10000000;
int c = 0;
do{
c++;
i += i;
cout << i << endl;
}while (i > 0);
cout << c << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
所以这是交易:有符号整数溢出在技术上是未定义的行为.但是x86上的GCC使用x86整数指令实现整数运算 - 它包含溢出.
因此,我本来期望它包装溢出 - 尽管事实上它是未定义的行为.但事实显然并非如此.那么我错过了什么?
我使用以下方法编译:
~/Desktop$ g++ main.cpp -O2
Run Code Online (Sandbox Code Playgroud)
GCC输出:
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
0
0
0
... (infinite loop)
Run Code Online (Sandbox Code Playgroud)
禁用优化后,没有无限循环且输出正确.Visual Studio也正确编译它并给出以下结果:
正确的输出:
~/Desktop$ g++ main.cpp
~/Desktop$ ./a.out
536870912
1073741824
-2147483648
3
Run Code Online (Sandbox Code Playgroud)
以下是一些其他变体:
i *= 2; // Also …Run Code Online (Sandbox Code Playgroud) 我找到了一个隐藏在这个小宝石后面的非常讨厌的小虫.我知道根据C++规范,有符号溢出是未定义的行为,但只有当值扩展到位宽时发生溢出sizeof(int).据我所知,只要增加a char就不应该是未定义的行为sizeof(char) < sizeof(int).但这并没有解释如何c获得一个不可能的价值.作为一个8位整数,如何c保持大于其位宽的值?
// Compiled with gcc-4.7.2
#include <cstdio>
#include <stdint.h>
#include <climits>
int main()
{
int8_t c = 0;
printf("SCHAR_MIN: %i\n", SCHAR_MIN);
printf("SCHAR_MAX: %i\n", SCHAR_MAX);
for (int32_t i = 0; i <= 300; i++)
printf("c: %i\n", c--);
printf("c: %i\n", c);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
SCHAR_MIN: -128
SCHAR_MAX: 127
c: 0
c: -1
c: -2
c: -3
...
c: -127
c: -128 // <= The next value …Run Code Online (Sandbox Code Playgroud) 请考虑以下代码:
#include <iostream>
struct foo
{
// (a):
void bar() { std::cout << "gman was here" << std::endl; }
// (b):
void baz() { x = 5; }
int x;
};
int main()
{
foo* f = 0;
f->bar(); // (a)
f->baz(); // (b)
}
Run Code Online (Sandbox Code Playgroud)
我们期望(b)崩溃,因为x空指针没有相应的成员.在实践中,(a)不会崩溃,因为this从不使用指针.
因为(b)取消引用this指针((*this).x = 5;),并且this为null,程序进入未定义的行为,因为取消引用null总是被称为未定义的行为.
会(a)导致未定义的行为吗?如果两个函数(和x)都是静态的呢?
c++ standards-compliance null-pointer undefined-behavior language-lawyer
我的印象是访问union除最后一个成员之外的成员是UB,但我似乎无法找到一个可靠的参考(除了声称它是UB但没有标准支持的答案).
那么,这是不确定的行为?
我要问的是众所周知的"结构的最后一个成员有可变长度"的技巧.它是这样的:
struct T {
int len;
char s[1];
};
struct T *p = malloc(sizeof(struct T) + 100);
p->len = 100;
strcpy(p->s, "hello world");
Run Code Online (Sandbox Code Playgroud)
由于结构在内存中的布局方式,我们可以将结构覆盖在一个大于必要的块上,并将最后一个成员视为大于1 char指定的大小.
所以问题是:这种技术在技术上是不确定的行为吗?.我希望它是,但很好奇标准对此有何看法.
PS:我知道C99的方法,我希望答案专门针对上面列出的技巧版本.
我很惊讶地意外地发现以下工作:
#include <iostream>
int main(int argc, char** argv)
{
struct Foo {
Foo(Foo& bar) {
std::cout << &bar << std::endl;
}
};
Foo foo(foo); // I can't believe this works...
std::cout << &foo << std::endl; // but it does...
}
Run Code Online (Sandbox Code Playgroud)
我将构造对象的地址传递给它自己的构造函数.这看起来像源级别的循环定义.标准是否真的允许您在构造对象之前将对象传递给函数,还是这种未定义的行为?
鉴于所有类成员函数已经将指向其类实例的数据的指针作为隐式参数,我认为这并不奇怪.并且数据成员的布局在编译时是固定的.
请注意,我不是在问这是有用还是好主意; 我只是在修补一些关于课程的更多信息.
有人告诉我,以下代码在 C++20 之前具有未定义的行为:
int *p = (int*)malloc(sizeof(int));
*p = 10;
Run Code Online (Sandbox Code Playgroud)
真的吗?
论点是int对象的生命周期在为其分配值之前没有开始(P0593R6)。要解决此问题,new应使用放置:
int *p = (int*)malloc(sizeof(int));
new (p) int;
*p = 10;
Run Code Online (Sandbox Code Playgroud)
我们真的必须调用一个微不足道的默认构造函数来启动对象的生命周期吗?
同时,代码在纯 C 中没有未定义的行为。但是,如果我int在 C 代码中分配 an并在 C++ 代码中使用它呢?
// C source code:
int *alloc_int(void)
{
int *p = (int*)malloc(sizeof(int));
*p = 10;
return p;
}
// C++ source code:
extern "C" int *alloc_int(void);
auto p = alloc_int();
*p = 20;
Run Code Online (Sandbox Code Playgroud)
它仍然是未定义的行为吗?