指向 int == 指向 char 的指针(有点)?

Pointer to int == Pointer to char (somewhat)?

在下面给出的代码中,我声明了一个指向 int 的指针,我们都知道 memcpy returns 一个指向目标字符串的空指针,所以如果 ptr 是一个指向 int 的指针,那么为什么 printf("%s",ptr); 完全有效,ptr 毕竟不是指向 char 的指针。

#include <stdio.h>
#include <string.h>
//Compiler version gcc  6.3.0

int main()
{
  char a1[20] ={0} , a2[20] ={0};
  int *ptr;
  fgets(a1,20,stdin);
  fgets(a2,20,stdin);
  ptr = memcpy(a1,a2,strlen(a2)-1);
  printf("%s \n",ptr);
  if(ptr)
  printf("%s",a1);
  return 0;
}

首先考虑ptr = memcpy(a1,a2,strlen(a2)-1);memcpy 声明为 void *memcpy(void * restrict, const void * restrict, size_t),因此它接受传递给它的 a1a2,因为指向任何不合格对象类型的指针可能会转换为 void *const void *。 (指向用 const 限定的对象类型的指针也可以转换为 const void *。)这遵循 C 2018 6.5.2.2 7 中函数调用的规则(参数被转换为参数类型,就像通过赋值一样) 和 6.5.16 1(一个操作数是一个可能限定的 void * 并且左侧具有右侧的所有限定符)和 6.5.16 2(右侧操作数转换为左侧的类型)。

然后 memcpy returns 一个 void * 这是它的第一个参数(在转换为 void * 之后),我们尝试将其分配给 ptr .这满足了赋值的约束(其中一个操作数是void *),所以它把指针转换为ptr的类型,即int *。这受 6.3.2.3 7:

约束

A pointer to an object type may be converted to a pointer to a different object type. If the resulting pointer is not correctly aligned for the referenced type, the behavior is undefined. Otherwise, when converted back again, the result shall compare equal to the original pointer…

由于 a1 是一个 char 数组,没有对齐要求,它可以有任何对齐方式。它可能不适合 int。如果是这样,那么 C 标准没有根据上述定义程序的行为。

如果 a1 恰好适合 int 或 C 实现成功转换它,我们继续 printf("%s \n",ptr);

printf 声明为 int printf(const char * restrict, ...)。对于 ... 对应的参数,没有要转换成的参数类型。相反,执行 默认参数 promotions。这些影响整数和 float 参数但不影响指针参数。所以 ptr 被传递给 printf 不变,作为 int *.

对于 %s 转换,7.21.6.1 8 中的 printf 规则规定“参数应为指向字符类型数组初始元素的指针。”虽然 ptr 指向内存中与初始元素相同的位置,但它是指向 int 的指针,而不是指向初始元素的指针。因此,这是错误的参数类型。

7.21.6.1 9 表示“......如果任何参数不是相应转换规范的正确类型,则行为未定义。”因此,C标准没有定义这个程序的行为。

在许多 C 实现中,指针是内存中的简单地址,int *char * 具有相同的表示形式,编译器将容忍将 int * 传递给 [=40] =]转换。在这种情况下,printf 接收到它期望的地址并将打印 a1 中的字符串。这就是为什么你观察到你所做的结果。 C 标准不需要这种行为。因为 printf 是标准 C 库的一部分,所以 C 标准允许编译器在使用外部链接调用它时对其进行特殊处理。假设编译器可以将参数视为具有正确的类型(即使它没有)并将 printf 调用更改为使用 ptr 的循环,就好像它是 char * .我不知道任何编译器会在这种情况下生成不需要的代码,但关键是 C 标准不禁止它。

why printf("%s",ptr); is totally valid

不是 - 它可能按预期工作,但不能保证。通过将错误类型的参数传递给 printf,您调用了未定义的行为,这仅意味着编译器不需要以任何特定方式处理这种情况。你可能会得到预期的输出,你可能会得到垃圾输出,你可能会得到运行时错误,你可能会破坏你的系统状态,你可能会打开一个通向宇宙另一边的黑洞。