调用未定义行为的代码(在此示例中,除以零)将永远不会执行,程序是否仍未定义行为?
int main(void)
{
int i;
if(0)
{
i = 1/0;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我认为它仍然是未定义的行为,但我在标准中找不到支持或否认我的任何证据.
那么,有什么想法吗?
正如初始化所述,需要进行左值到右值的转换?是int x = x;UB吗?C++标准在3.3.2 声明部分中有一个令人惊讶的例子,其中a int用它自己的不确定值初始化:
Run Code Online (Sandbox Code Playgroud)int x = 12; { int x = x; }这里第二个x用它自己的(不确定的)值初始化.- 结束例子 ]
Johannes对此问题的回答表明是未定义的行为,因为它需要左值到右值的转换.
在最新的C++ 14草案标准中N3936,可以在此处找到此示例已更改为:
Run Code Online (Sandbox Code Playgroud)unsigned char x = 12; { unsigned char x = x; }这里第二个x用它自己的(不确定的)值初始化.- 结束例子 ]
C++ 14中有关于不确定值和未定义行为的变化,这些变化在示例中引发了这种变化吗?
我正在研究核心常量表达式*中允许的内容,这在C++标准草案的5.19 常量表达式第2段中有所描述:
条件表达式是核心常量表达式,除非它涉及以下之一作为潜在评估的子表达式(3.2),但是未评估的逻辑AND(5.14),逻辑OR(5.15)和条件(5.16)操作的子表达式不考虑[注意:重载的运算符调用函数.-end note]:
并列出随后的子弹中的排除项并包括(强调我的):
- 具有未定义行为的操作 [注意:包括,例如,有符号整数溢出(第5条),某些指针算术(5.7),除零(5.6)或某些移位操作(5.8) - 结束注释];
嗯?为什么常量表达式需要此子句来涵盖未定义的行为?常量表达式是否有一些特殊的东西需要未定义的行为才能在排除中进行特殊划分?
拥有这个条款是否给了我们没有它的任何优势或工具?
作为参考,这看起来像广义常量表达式提案的最新修订版.
我不确定我是否完全理解未定义行为可能危及程序的程度.
假设我有这段代码:
#include <stdio.h>
int main()
{
int v = 0;
scanf("%d", &v);
if (v != 0)
{
int *p;
*p = v; // Oops
}
return v;
}
Run Code Online (Sandbox Code Playgroud)
这个程序的行为是否仅针对v非零的情况而定义,或者即使v为零也未定义?
注意:我见过类似的问题,但没有一个答案是准确的,所以我自己也会这样问.
C++标准说:
程序可以通过重用对象占用的存储来结束任何对象的生命周期,或者通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期.对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数; 但是,如果没有对析构函数的显式调用或者如果没有使用delete-expression来释放存储,则不应该隐式调用析构函数,并且任何依赖于析构函数产生的副作用的程序都有未定义的行为.
我根本不明白"取决于副作用"是什么意思.
一般问题是:
举例说明我的观点是:
考虑下面这样的程序.还要考虑明显的变化(例如,如果我不在另一个上构造一个对象,但我仍然忘记调用析构函数,如果我不打印输出来观察它,等等):
#include <math.h>
#include <stdio.h>
struct MakeRandom
{
int *p;
MakeRandom(int *p) : p(p) { *p = rand(); }
~MakeRandom() { *p ^= rand(); }
};
int main()
{
srand((unsigned) time(NULL)); // Set a random seed... not so important
// In C++11 we could use std::random_xyz instead, that's not the point
int x = 0;
MakeRandom *r = …Run Code Online (Sandbox Code Playgroud) C++标准配备的用于定义一个惊人的数不清楚1种,其意味着与细微的差别或多或少相同的行为.读到这个答案后,我注意到"该程序格式错误;无需诊断"的措辞.
实现定义与未指定的行为的不同之处在于前一种情况下的实现必须清楚地记录它正在做什么(在后一种情况下,它不需要),两者都是格式良好的.未定义的行为与未指定的行为不同,因为程序是错误的(1.3.13).
否则它们都有一个共同点,就是标准不会对实现的内容做出任何假设或要求.除1.4/8之外,其中声明实现可能具有不会改变格式良好的程序的行为的扩展,但根据标准是不正确的,并且实现必须诊断使用这些,但之后可以继续编译和执行不正当的计划.
一个病态的程序否则只能定义为没有很好地形成(太棒了!).甲合式程序,在另一方面,被定义为一个附着在语法和诊断的语义规则.因此,这意味着错误的程序是打破语法或语义规则(或两者)的程序.换句话说,一个不正确的程序实际上根本不应该编译(如何以任何有意义的方式翻译例如具有错误语法的程序?).
我倾向于认为错误这个词也意味着编译器应该使用错误消息中止构建(毕竟,错误表明存在错误),但1.3.13中的"注意"部分明确允许不同的东西,包括默默地忽略问题(并且编译器显然不会因为UB 而破坏构建,大多数甚至不会默认警告).
人们可能会进一步认为错误和不正确的形式是相同的,但如果情况或该词应该是什么意思,那么标准就不会详细说明.
此外,1.4表示
符合要求的实施应[...]接受并正确执行格式良好的程序
和
如果程序包含违反不需要诊断的规则,则不要求对该程序进行实施.
换句话说,符合要求的实现必须接受格式良好的程序,但它也可能接受形式错误的程序,甚至没有警告.但是,如果程序因为使用扩展而格式不正确.
第二段建议任何与"无需诊断"相关的内容意味着规范中没有要求,这意味着它大部分等同于"未定义的行为",除非没有提到错误.
因此,使用"形成不良;无需诊断"等措辞背后的意图是什么?
"无诊断"的存在表明它与未定义的行为完全相同(或大部分相同?).此外,由于实现定义和未指定的行为被定义为格式良好,因此它必须是不同的.
另一方面,由于格式错误的程序会破坏语法/语义规则,因此它实际上不应该编译.但是,与"不需要诊断"相结合意味着允许编译器在没有警告的情况下以静默方式退出,并且之后您将无法找到可执行文件.
"形成不良;无需诊断"和"未定义行为"之间是否存在差异,或者这只是同一事物的复杂同义词?
在另一篇文章的评论中提出了这个讨论.
示例代码:
#include <string>
void func()
{
std::string* foo;
foo = new std::string[125];
throw 1;
delete [] foo;
}
int main()
{
try { func(); }
catch(int x) {}
}
Run Code Online (Sandbox Code Playgroud)
该程序是否会导致未定义的行为?据称,根据§3.8p4,它是UB ,它是:
程序可以通过重用对象占用的存储来结束任何对象的生命周期,或者通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期.对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数; 但是,如果没有显式调用析构函数或者如果没有使用delete-expression(5.3.5)来释放存储,则不应该隐式调用析构函数,并且任何程序都依赖于析构函数产生的副作用有未定义的行为.
在 C++ 中,如果我正确理解措辞,编译器可以假设不会发生 UB,从而影响将遇到 UB 但尚未遇到的执行路径中的行为(甚至是 I/O 等可见的副作用)。
在抽象机遇到 UB 之前,C 是否需要“正确”执行程序直至最后可见的副作用?编译器似乎以这种方式运行,但在 C++ 模式和 C 模式下都是如此,因此这可能只是错过了优化,或者是有意选择减少“对程序员的敌意”。
ISO C 标准是否允许这样的优化? (出于各种原因,编译器仍然可能合理地选择不这样做,包括在不错误编译任何其他情况的情况下实现困难,或“实现质量”因素。)
这个问题(主要)是关于 C 的,但 C++ 至少是一个有趣的比较点,因为 UB 的概念在两种语言中至少是相似的。我在 ISO C 中没有看到任何类似的显式语言,因此提出了这个问题。
ISO C++ [intro.abstract]/5是这么说的(至少从 C++11 开始,可能更早):
执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。但是,如果任何此类执行包含未定义的操作,则本文档对使用该输入执行该程序的实现没有任何要求(甚至不涉及第一个未定义操作之前的操作)。
我认为对使用该输入执行该程序的实现没有要求的预期含义是,即使在抽象机遇到 UB 之前排序的可见副作用(例如访问volatile或包括 unbuffered 的 I/O fprintf(stderr, ...))也不需要即将发生。
“用该输入执行该程序”这句话是指整个程序,从执行开始就开始。(有些人谈论“时间旅行”,但这实际上是一个问题,例如后来的代码允许值范围假设(例如非空)影响编译时决策中的早期分支,正如其他人将其放在上一个SO问题。编译器可以假设整个程序的执行不会遇到UB。)
我试图让编译器来完成我想知道的优化。这非常明确地表明根据编译器开发人员对该标准的解释是允许的。(除非它实际上是一个编译器错误。)但到目前为止我所尝试的一切都表明编译器保留了可见的副作用。
我只尝试过volatile访问(不是putchar或std::cout<<或其他),假设优化器应该更容易查看和理解。对非内联函数的调用通常printf是优化器的黑匣子,除非它们是基于函数名称的特殊情况,例如一些非常重要的函数,例如memcpy. 此外,假设对 I/O 函数的调用可能会永远阻塞,甚至可能中止,因此在以后的代码中永远不会遇到 UB。
实际上我只尝试过volatile商店,没有尝试过 …
c gcc compiler-optimization undefined-behavior language-lawyer
事实证明,许多无辜的东西都是C++中未定义的行为.例如,一旦一个非空的指针已被delete"D 甚至在打印的是指针的值是未定义的行为.
现在内存泄漏肯定是坏事.但他们是什么类的情况 - 定义,未定义或其他类别的行为?
我知道未定义的行为可能会导致任何事情,这使得任何包含UB的程序都可能毫无意义.我想知道是否有任何方法可以确定程序中最早的一点,即未定义的行为可能会导致问题.这是一个例子来说明我的问题.
void causeUndefinedBehavior()
{
//any code that causes undefined behavior
//every time it is run
char* a = nullptr;
*a;
}
int main()
{
//code before call
//...
causeUndefinedBehavior();
//code after call
//...
}
Run Code Online (Sandbox Code Playgroud)
根据我的理解,可能引发未定义行为的可能时间(不一定表现出来)是:
causeUndefinedBehavior()编译.main()编译.causeUndefinedBehavior()执行.或者,对于每个案例和每个实现,未定义的行为都会被完全不同?
另外,如果我注释掉causeUndefinedBehavior()调用的行,是否会消除UB,或者它是否仍然在程序中,因为编译了包含UB的代码?