我想了解以下代码是否(总是,有时或从不)根据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因此,“从另一面”来看,以下strncpy()实现是符合要求的:
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}\nRun Code Online (Sandbox Code Playgroud)\n\n当然,这是愚蠢的代码,一个健全的实现例如只是初始化char c = 1,所以如果您在野外发现一个 C 实现会为您的代码表现出意想不到的行为,我会感到惊讶。
还有一个参数支持*s2在任何情况下都允许访问符合要求的实现:C 中不允许使用零大小的数组。因此,如果s2应该是指向数组的指针,*s2则必须有效。这与你引用的\xc2\xa77.1.4的措辞密切相关