2

This question might make no sense, but I'll ask anyway with an example. Does this code exhibit undefined behaviour?

int main() {
    int a, b; // uninitialised

    memcpy(&a, &b, sizeof(int));
}

I would usually say yes, because causing an lvalue-to-rvalue conversion of an uninitialised object is UB, something which must be done to copy the bytes of b to a.

However, memcpy may or may not be implemented in C++. If memcpy is written in assembly for example, then there are no such rules. Do programs that do things that would normally cause undefined behaviour still cause it if they outsource the offending operations to other languages with dissimilar rules?

4

5 回答 5

2

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

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

于 2013-02-06T21:56:15.133 回答
2

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

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

唯一有点相关的报价来自 7.4

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

从 7.5

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

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

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

于 2013-02-06T23:45:55.690 回答
1

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

在 C89 和 C99 基本原理中,C 委员会说:

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

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

于 2013-02-06T22:22:10.173 回答
1

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

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

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

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

澄清一下:我不是那些了解 C 或 C++ 标准每一部分的每一段的人。我以编写代码为生,并且我对处理器和连接硬件的方式有足够的了解,可以理解为什么规范说“当你......时这是未定义的行为”[它可能根本不使用这些词,因为标准没有' t use second person] - 在使用尚未初始化的变量的情况下,C 语言不会尝试强制执行任何特定行为,因为它可能会限制语言在特定平台上使用,因为平台可以'不保证该行为[如果您指定一种行为,迟早会有人依赖该行为,使其成为在每个平台上实现的必要部分]。

于 2013-02-06T22:02:53.443 回答
0

这指的是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++ 标准无关。

于 2013-02-07T21:34:47.513 回答