在分配内存后使用指针是否是未定义的行为?

vbe*_*nar 17 c undefined-behavior language-lawyer

我有以下代码:

uint8_t buffer[16];
uint8_t data[16];
uint8_t buffer_length = 16;
uint8_t data_length = 0;

memcpy(buffer + buffer_length, data, data_length);
Run Code Online (Sandbox Code Playgroud)

memcpy应该是空操作,因为data_length为零。然而buffer + buffer_length,点就在分配的内存之外。我想知道它是否会触发某种未定义的行为?我应该memcpy用一个额外的包起来吗if

我知道任何合理的实现memcpy都会很好地工作,但是这个问题更多地是从代码正确性的角度出发并避免未定义的行为。

nie*_*sen 22

正如 Stephen C 的回答所指出的,C17 规范对于这是否定义良好有点模糊。

然而,C23 规范在 7.1.4 部分的脚注中澄清了这一点,指出

如果函数参数被描述为数组,则传递给函数的指针应具有一个值,以便所有地址计算和对对象的访问(如果指针确实指向此类数组的第一个元素,则这将是有效的)有效的。

脚注(235)如下:

例如,这包括传递一个指向数组末尾一位且大小为 0 的有效指针,或者使用大小为 0 的任何有效指针。

句子的第一部分明确地将 OP 情况定义为明确定义的。

添加此声明可以被视为承认 C17 规范在这一点上不够明确,因此不能排除 C17 编译器的实现者可能会善意地解释该标准,从而使这种情况不是定义的行为。

然而,C23 应该消除这种不确定性。

  • @Lundin 这是正确的,但在这种情况下,我认为脚注是对规范性段落应如何解释的澄清。它没有添加到规范中。 (4认同)
  • 是的,这是一个很好的发现 - 我认为在 C23 之前没有必要进行澄清,但显然有些人需要澄清。如果说第 7 章(标准库)中的某些内容使第 6 章(C 语言)中规定的规则无效,这种想法通常是模糊的。标准库不需要用 C 语言实现,但函数 API 肯定需要用 C 语言实现,因此(在大多数情况下)适用于标准库函数的规则与适用于任何 C 函数的规则相同。 (2认同)

Lun*_*din 9

该代码具有明确定义的行为。

\n

函数在状态下排序的“字符串处理函数” memcpy(C17 7.24.1):

\n
\n

如果参数声明为size_t n指定函数的数组长度,n则在调用该函数时可以将值设置为零。除非在本节中特定函数的描述中另有明确说明,否则此类调用中的指针参数仍应具有有效值,如 7.1.4 中所述。

\n
\n

C17 7.1.4 中有关传递给标准库函数的数组参数的部分有些相关:

\n
\n

如果函数参数被描述为数组,则实际传递给函数的指针应具有一个值,以便所有地址计算和对对象的访问(如果指针确实指向此类数组的第一个元素,则该值将有效)数组)实际上是有效的。

\n
\n

(然而,参数memcpy不一定是一个或多个数组。但在本例中它们都是。)

\n

地址计算和对数组一项的以下访问是由指针算术规则定义的,特别是关于加法运算符的 C17 6.5.6 \xc2\xa78,相关部分是这个:

\n
\n

如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出;否则,行为是未定义的。如果结果指向数组对象的最后一个元素,则不得将其用作所求值的一元 * 运算符的操作数。

\n
\n

因此,只要我们不取消引用该位置,“将一项指向数组末尾”这一特殊规则buffer + buffer_length明确允许。在这种情况下不会发生这种情况。如果我们buffer + buffer_length + 1这样写,这将是无效的地址计算和未定义的行为。

\n