未定义的行为-是用其他语言编写的函数,受C++关于UB的规则的约束

undefined behavior - Are functions written in other languages subject to the rules of C++ regarding UB?

本文关键字:函数 C++ UB 约束 规则 关于 语言 其他 未定义      更新时间:2023-10-16

这个问题可能没有意义,但我还是会举个例子来问。此代码是否表现出未定义的行为?

int main() {
    int a, b; // uninitialised
    memcpy(&a, &b, sizeof(int));
}

我通常会说是的,因为导致未初始化对象的左值到右值转换是UB,必须这样做才能将b的字节复制到a

然而,memcpy可以或者可以不在C++中实现。例如,如果memcpy是在汇编中编写的,那么就没有这样的规则。如果程序将违规操作外包给具有不同规则的其他语言,那么那些通常会导致未定义行为的程序是否仍然会导致这种行为?

这有点像在问"在C++中,过度烧焦牛排会导致未定义行为吗?"

所有未定义的行为意味着C++(或C等)标准不能保证在翻译和/或执行程序时会发生什么。毫不奇怪,C++标准对其他语言的函数没有太多说明。

唯一相关的报价来自7.4

有条件地支持asm声明;其含义是实现定义的。

和7.5

[在链接规范语法中…]本国际标准指定字符串文字"C""C++"的语义。有条件地支持使用除"C""C++"之外的字符串文字,并使用实现定义的语义。

因此,基本上,可以将其他语言与C++一起使用,但除了将这些部分粘合在一起所需的语法之外,本文不会讨论其他语言。

从C++标准的角度来看,其他语言的函数对C++程序具有实现定义的影响。实现定义通常被认为比未定义行为更好,尽管不可移植。但使用C++和C之外的东西并不一定能移植到每个C++实现中,这并不奇怪。

在您描述的情况下,a由C++程序分配并传递给memcpy()。这意味着行为仍然没有定义。

但是,这并不意味着行为将是随机的或在运行之间发生变化。未定义表示未定义行为。这意味着您不能依赖任何特定的行为,包括程序中断。在C或C++中调试的一些最困难的问题是编译器将未定义的结构转换为按预期工作的结构。然后,当您更改编译器标志时,事情突然停止了工作。

实现可以自由扩展语言,并将定义的行为赋予C未定义的行为。在读取具有自动存储持续时间的未初始化对象(C中的UB)的示例中,它可以说其值未指定,但评估对象不会调用未定义的行为。

在C89和C99的比例中,C委员会表示:

未定义的行为允许实现者不捕捉某些难以诊断的程序错误。它还确定了可能的一致语言扩展领域:实现者可以通过提供官方未定义行为的定义来增强语言。*

这样的程序对于该实现将是有效的程序,但仍然是无效的C程序。

UB不是一个特定于语言的"东西"。这是"你所做的事情没有明确的行为"。因此,"使用未初始化的内存是未定义的"的原因是,C语言无法规定如果读取未写入的内存应该发生什么。正如另一个答案中所讨论的,如果内存未初始化,它可能会出现奇偶校验或ECC错误,因为奇偶校验位在第一次写入时设置正确。当内存包含"通电后的内容"时,它很可能具有错误的奇偶校验/ecc值。

ECC或奇偶校验错误通常会导致系统停止,因为预计不会有坏内存!

因此,重要的不是你执行的代码是用什么语言编写的,而是"如果你从尚未初始化的内存中读取,那么行为可能会出错"。读取内存的行为,无论代码是用C、C++、汇编程序、Pascal、Fortran还是LisP编写的。

请记住,未定义并不一定是"坏事发生",只是"规范没有解释结果是什么,坏事可以在UB中发生"。除以零并不能保证你的程序崩溃——它很可能会崩溃,但它也可能只会给你返回与你在/的另一边输入的值相同的值——这将是完全有效的UB。读取未初始化的内存可能会导致"你得到零"、"你得到所有的一"、"得到一些一和零的混合物,没有人知道是哪一个",或者"可能导致系统因疑似内存错误而重新启动"。当然,也可能每次都不一样——例如,有时奇偶校验位是"正确的",有时不是。

澄清一下:我不是那种知道C或C++标准每一节每一段的人。我以编写代码为生,我对处理器和连接的硬件有足够的了解,可以理解为什么规范会说"当你……时这是未定义的行为"(它可能根本不使用这些词,因为标准不使用第二人称)——在使用未初始化的变量的情况下,C语言不会试图强制执行任何特定的行为,因为它可能会限制语言在特定平台上使用,因为平台无法保证这种行为[如果你指定了一种行为,迟早会有人依赖这种行为,使其成为在每个平台上实现的必要部分]。

这是指我建议的http://blog.regehr.org/archives/213。如果你想争论以下定义,请在那里讨论。IMO,这是你所问的问题的核心,大多数答案都试图传达。你可以自由表达不同意见。

让我们考虑一下其中的#1和#2:

Type 1: Behavior is defined for all inputs 
Type 2: Behavior is defined for some inputs and undefined for others 
Type 3: Behavior is undefined for all inputs 

类型1表示函数会引发错误和异常,以防止进入UB。类型2意味着代码不会引发所有可能的错误和异常,并且可能进入UB。

重点是:您正在使用一个文档化的库调用。让我们使用strcpy或gets的例子。众所周知,您可以为这些库调用提供长字符串和溢出内存,从而导致UB。有一些实现定义的约束可能会阻止这种情况,但您更清楚。所以,你的问题实际上与标准无关,而是关于库调用的文档所说的内容,以及它是类型1还是类型2。strcpy、gets和其他库函数显然可能是类型2。因此,这是对图书馆用户的一个警告。我是2型,一旦被激怒,我可能会爆炸。

IMO,标准无法处理库附加组件,因为它们是由实现定义的。所以答案是:它与C++标准无关。