首先考虑ptr = memcpy(a1,a2,strlen(a2)-1);
。 memcpy
被声明为 void *memcpy(void * restrict,const void * restrict,size_t)
,因此它接受传递给它的 a1
和 a2
,因为指向任何非限定对象类型的指针可能会被转换为 void *
或const void *
。 (指向用 const
限定的对象类型的指针也可以转换为 const void *
。)这遵循 C 2018 6.5.2.2 7 中函数调用的规则(参数被转换为参数类型,就像通过赋值) 和 6.5.16 1(一个操作数是一个可能限定的 void *
并且左边有右边的所有限定符)和 6.5.16 2(右边的操作数被转换为左边的类型)。
然后 memcpy
返回一个 void *
作为它的第一个参数(转换为 void *
后,我们尝试将其分配给 ptr
。这满足赋值(其中一个操作数是 void *
),因此它将指针转换为 ptr
的类型,即 int *
。这受 6.3.2.3 7 约束:
指向对象类型的指针可以转换为指向不同对象类型的指针。如果结果指针未针对引用类型正确对齐,则行为未定义。否则,当再次转换回来时,结果将比较等于原始指针......
由于 a1
是一个没有请求对齐的 char
数组,它可以有任何对齐。它可能不适合 int
。如果是这样,则 C 标准没有定义程序的行为,如上所述。
如果 a1
恰好与 int
对齐,或者 C 实现无论如何都成功转换了它,我们继续printf("%s \n",ptr);
。
printf
被声明为 int printf(const char * restrict,...)
。对于对应于 ...
的参数,没有要转换为的参数类型。而是执行默认参数提升。这些影响整数和 float
参数,但不影响指针参数。因此,ptr
作为 printf
原样传递给 int *
。
对于 %s
转换,7.21.6.1 8 中的 printf
规则说“参数应是指向字符类型数组的初始元素的指针。”虽然 ptr
与初始元素指向内存中的相同位置,但它是指向 int
的指针,而不是指向初始元素的指针。因此,这是错误的参数类型。
7.21.6.1 9 说“……如果任何参数不是相应转换规范的正确类型,则行为未定义。”因此,C 标准并未定义该程序的行为。
在许多 C 实现中,指针是内存中的简单地址,int *
和 char *
具有相同的表示形式,并且编译器会容忍为 int *
传递 %s
转换。在这种情况下,printf
接收到它期望的地址并将在 a1
中打印字符串。这就是为什么你观察你所做的结果。 C 标准不需要这种行为。因为 printf
是标准 C 库的一部分,所以 C 标准允许编译器在使用外部链接调用时对其进行特殊处理。假设编译器可以将参数视为具有正确的类型(即使它没有)并将 printf
调用更改为使用 ptr
的循环,就好像它是一个 char *
.我不知道在这种情况下有任何编译器会生成不需要的代码,但关键是 C 标准并没有禁止它。
,
为什么 printf("%s",ptr);完全有效
它不是 - 它可能按预期工作,但不能保证。通过将错误类型的参数传递给 printf
,您调用了未定义的行为,这仅意味着编译器不需要以任何特定方式处理这种情况。你可能会得到预期的输出,你可能会得到垃圾输出,你可能会得到运行时错误,你可能会破坏你的系统状态,你可能会打开一个通往宇宙另一端的黑洞。
本文链接:https://www.f2er.com/909.html