你的第三个例子:
printf("%s",(char *){'H','i','\0'});
甚至是不合法的(严格来说它是违反约束的),而且你应该在编译它时至少得到一个警告。当我使用带有默认选项的 gcc 编译它时,我收到了 6 个警告:
c.c:3:5: warning: initialization makes pointer from integer without a cast [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
to 的第二个参数printf
是复合文字。具有 type 的复合文字是合法的(但很奇怪)char*
,但在这种情况下,复合文字的初始化列表部分是无效的。
打印警告后,gcc 似乎正在做的是(a)将'H'
类型为的表达式转换int
为char*
,产生一个垃圾指针值,以及(b)忽略初始化器元素的其余部分,'i'
以及'\0'
。结果是一个char*
指向(可能是虚拟的)地址的指针值0x48
——假设是基于 ASCII 的字符集。
忽略多余的初始化器是有效的(但值得警告),但没有从int
to的隐式转换char*
(除了空指针常量的特殊情况,这在此处不适用)。gcc 通过发出警告完成了它的工作,但它可能(而且恕我直言应该)用一个致命的错误消息拒绝它。它将通过该-pedantic-errors
选项执行此操作。
如果你的编译器警告你这些行,你应该在你的问题中包含这些警告。如果没有,要么提高警告级别,要么获得更好的编译器。
更详细地了解在这三种情况下会发生什么:
printf("%s","Hi");
AC 字符串字面量类似于"%s"
or"Hi"
创建一个匿名静态分配的数组char
。(此对象不是const
,但尝试修改它具有未定义的行为;这并不理想,但有其历史原因。)'\0'
添加终止空字符以使其成为有效字符串。
数组类型的表达式,在大多数情况下(例外是当它是一元运算符sizeof
或&
运算符的操作数,或者当它是用于初始化数组对象的初始化程序中的字符串文字时)被隐式转换为 ("decays to") a指向数组第一个元素的指针。所以传递给的两个参数printf
是类型char*
;printf
使用这些指针遍历各自的数组。
printf("%s",(char[]){'H','i','\0'});
这使用了由 C99(ISO C 标准的 1999 年版)添加到语言中的一个特性,称为复合文字。它类似于字符串文字,因为它创建一个匿名对象并引用该对象的值。复合文字具有以下形式:
( type-name ) { initializer-list }
并且该对象具有指定的类型并被初始化为初始化列表给出的值。
以上几乎等同于:
char anon[] = {'H', 'i', '\0'};
printf("%s", anon);
同样,第二个参数printf
指向一个数组对象,它“衰减”为指向数组第一个元素的指针;printf
使用该指针遍历数组。
最后,这个:
printf("%s",(char*){'A','B','\0'});
正如你所说,失败了。复合文字的类型通常是数组或结构(或联合);我实际上没有想到它可能是标量类型,例如指针。以上几乎等同于:
char *anon = {'A', 'B', '\0'};
printf("%s", anon);
显然anon
是 type char*
,这是对格式printf
的期望。"%s"
但是初始值是多少?
该标准要求标量对象的初始化程序是单个表达式,可以选择用花括号括起来。但由于某种原因,该要求属于“语义”,因此违反它不是违反约束;这只是未定义的行为。这意味着编译器可以做任何它喜欢的事情,并且可能会或可能不会发出诊断。gcc 的作者显然决定发出警告并忽略列表中除第一个初始值设定项之外的所有内容。
之后,它就等同于:
char *anon = 'A';
printf("%s", anon);
常量'A'
是类型int
(由于历史原因,它是int
而不是char
,但同样的论点适用于任何一种方式)。没有从int
to的隐式转换char*
,实际上上面的初始化程序是违反约束的。这意味着编译器必须发出诊断(gcc 会),并且可能会拒绝程序(gcc 不会,除非您使用-pedantic-errors
)。一旦发出诊断,编译器就可以为所欲为;行为未定义(在这一点上有一些语言律师的分歧,但这并不重要)。gcc 选择将A
from的值转换int
为char*
(可能由于历史原因,可以追溯到 C 的类型比现在更弱的时候),导致垃圾指针的表示可能看起来像0x00000041
0x0000000000000041`。
然后将该垃圾指针传递给printf
,它尝试使用它来访问内存中该位置的字符串。欢闹随之而来。
有两个重要的事情要记住:
如果您的编译器打印警告,请密切注意它们。gcc 特别针对许多事情发出警告,恕我直言应该是致命错误。除非您完全理解警告的含义,否则永远不要忽略警告,足以让您的知识覆盖编译器作者的知识。
数组和指针是非常不同的东西。C 语言的几条规则似乎合在一起使它看起来是一样的。你可以暂时摆脱假设数组只不过是伪装的指针,但这种假设最终会反过来咬你。阅读comp.lang.c FAQ的第 6 部分;它比我更好地解释了数组和指针之间的关系。