带有一个过去指针的strncpy(d,s,0)

ano*_*nol 9 c language-lawyer

我想了解以下代码是否(总是,有时或从不)根据C11明确定义:

#include <string.h>
int main() {
  char d[5];
  char s[4] = "abc";
  char *p = s;
  strncpy(d, p, 4);
  p += 4; // one-past end of "abc"
  strncpy(d+4, p, 0); // is this undefined behavior?
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

C11 7.24.2.4.2说:

strncpy函数从s2指向的数组复制不超过n个字符(空字符后面的字符不被复制)到s1指向的数组.

请注意,这s2是一个数组,而不是一个字符串(所以当p == s+4没有问题时缺少null-terminator ).

7.24.1(字符串函数约定)适用于此(强调我的):

如果声明为size_t n的参数指定了函数数组的长度,则在调用该函数时,n的值可以为零.除非在本子条款中对特定函数的描述中另有明确说明,否则此类调用上的指针参数仍应具有有效值,如7.1.4中所述.在这样的调用中,定位字符的函数不会发生,比较两个字符序列的函数返回零,复制字符的函数复制零个字符.

上述7.1.4的相关部分是(强调我的):

7.1.4库函数的使用

除非在以下详细说明中另有明确说明,否则以下每个语句均适用:如果函数的参数具有无效值(例如函数域外的值,或程序地址空间外的指针,或者一个空指针,或指向不可修改的存储的指针,当相应的参数不是const限定的)或一个类型(提升后),具有可变数量的参数的函数不期望,行为是未定义的.如果一个函数参数被描述为一个数组,那么实际传递给该函数的指针应该具有一个值,使得所有地址计算和对象的访问(如果指针确实指向这样一个数组的第一个元素,这将是有效的)事实上是有效的.

我在解析最后一部分时遇到了一些麻烦.n == 0如果我可以假设我的实现在这种情况下不会计算任何地址,那么"所有地址计算和对对象的访问"似乎很容易满足.

换句话说,在严格解释标准时,我是否应该总是拒绝该计划?我应该一直允许吗?或者它的正确性是否依赖于实现(即,如果实现在检查之前计算第一个字符的地址n,那么上面的代码有UB,否则它没有)?

小智 2

您突出显示的部分:

\n\n
\n

实际传递给函数的指针应具有一个值,以便所有地址计算和对对象的访问实际上都是有效的。

\n
\n\n

清楚地表明您的代码确实无效。在谈论零参数的部分中size_t

\n\n
\n

在此类调用中,定位字符的函数未找到任何匹配项,比较两个字符序列的函数返回零,而复制字符的函数则复制零个字符。

\n
\n\n

无法保证复制函数不会尝试访问任何内容。

\n\n

因此,“从另一面”来看,以下strncpy()实现是符合要求的:

\n\n
char *strncpy(char *s1, const char *s2, size_t n)\n{\n    size_t i = 0;\n    char c = *s2;\n\n    while (i < n)\n    {\n        if (c) c = s2[i];\n        s1[i++] = c;\n    }\n    return s1;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当然,这是愚蠢的代码,一个健全的实现例如只是初始化char c = 1,所以如果您在野外发现一个 C 实现会为您的代码表现出意想不到的行为,我会感到惊讶。

\n\n
\n\n

还有一个参数支持*s2在任何情况下都允许访问符合要求的实现:C 中不允许使用零大小的数组。因此,如果s2应该是指向数组的指针,*s2则必须有效。这与你引用的\xc2\xa77.1.4的措辞密切相关

\n