Tho*_*hom 7 c arrays string pointers casting
我真的需要帮助.它已经动摇了我在C.Long的基础,我将非常感谢详细的答案.我把我的问题分成两部分.
答:为什么printf("%s",(char[]){'H','i','\0'});工作和打印Hi方式与传统方法printf("%s","Hi");一样?我们可以用(char[]){'H','i','\0'}它来代替"Hi"我们C代码中的任何地方吗?它们的含义是否相同?我的意思是,当我们用"Hi"C语言编写时,它通常意味着Hi存储在内存中的某个地方,传递给它的指针.可以说是看似丑陋的.(char[]){'H','i','\0'}他们完全一样吗?
B:当printf("%s",(char[]){'H','i','\0'})工作成功时,printf("%s","Hi")为什么然后printf("%s",(char*){'A','B','\0'}失败大时间和seg-fault如果我运行它尽管有警告?它让我感到惊讶,因为在C中,不char[]应该分解成char*,就像我们在函数参数中传递它一样,为什么它在这里没有这样做并且char*给出失败?我的意思是,不是char demo[]作为参数传递给功能相同char demo*?为什么结果在这里不相同?
请帮我解决这个问题.我觉得我还没有理解CI非常失望的基础知识.谢谢!
你的第三个例子:
printf("%s",(char *){'H','i','\0'});
Run Code Online (Sandbox Code Playgroud)
甚至不合法(严格来说这是违反约束条款),编译时你应该至少得到一个警告.当我用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]
Run Code Online (Sandbox Code Playgroud)
第二个参数printf是复合文字.具有类型的复合文字是合法的(但很奇怪)char*,但在这种情况下,复合文字的初始化列表部分无效.
在打印警告之后,gcc似乎正在做的是(a)将表达式'H'(类型)转换int为char*,产生垃圾指针值,以及(b)忽略初始化元素的其余部分,'i'以及'\0'.结果是char*指向(可能是虚拟的)地址的指针值0x48- 假设基于ASCII的字符集.
忽略多余的初始值设定项是有效的(但值得警告),但没有隐式转换int为char*(除了空指针常量的特殊情况,这里不适用).gcc通过发出警告完成了它的工作,但它可以(和恕我直言)应该用一个致命的错误消息拒绝它.它将使用该-pedantic-errors选项.
如果您的编译器警告您这些行,那么您应该在问题中包含这些警告.如果没有,请提高警告级别或获得更好的编译器.
详细了解三种情况中的每一种情况:
printf("%s","Hi");
Run Code Online (Sandbox Code Playgroud)
AC字符串文字像"%s"或"Hi"创建匿名静态分配的数组char.(此对象不是const,但尝试修改它具有未定义的行为;这不是理想的,但有历史原因.)'\0'添加终止空字符以使其成为有效字符串.
数组类型的表达式,在大多数情况下(例外情况是它是一元sizeof或&运算符的操作数,或者当它是用于初始化数组对象的初始化程序中的字符串文字时)被隐式转换为("衰变为")a指向数组第一个元素的指针.所以传递给的两个参数printf都是类型的char*; printf使用那些指针遍历各自的数组.
printf("%s",(char[]){'H','i','\0'});
Run Code Online (Sandbox Code Playgroud)
这使用了C99(1999年版的ISO C标准)添加到语言中的功能,称为复合文字.它类似于字符串文字,因为它创建一个匿名对象并引用该对象的值.复合文字的形式如下:
( type-name ) { initializer-list }
Run Code Online (Sandbox Code Playgroud)
并且对象具有指定的类型,并初始化为初始化列表给出的值.
以上几乎相当于:
char anon[] = {'H', 'i', '\0'};
printf("%s", anon);
Run Code Online (Sandbox Code Playgroud)
同样,第二个参数printf引用一个数组对象,它"衰减"到指向数组第一个元素的指针; printf使用该指针遍历数组.
最后,这个:
printf("%s",(char*){'A','B','\0'});
Run Code Online (Sandbox Code Playgroud)
正如你所说,失败了很多时间.复合文字的类型通常是数组或结构(或联合); 实际上我没有想到它可能是一个标量类型,如指针.以上几乎相当于:
char *anon = {'A', 'B', '\0'};
printf("%s", anon);
Run Code Online (Sandbox Code Playgroud)
显然anon属于类型char*,这是格式所printf期望的"%s".但最初的价值是什么?
该标准要求标量对象的初始值设定项为单个表达式,可选择用大括号括起来.但由于某种原因,该要求属于"语义学",因此违反它并不违反约束条件; 它只是未定义的行为.这意味着编译器可以执行任何喜欢的操作,并且可能会也可能不会发出诊断信息.gcc的作者显然决定发出警告并忽略列表中的第一个初始化程序.
之后,它变得相当于:
char *anon = 'A';
printf("%s", anon);
Run Code Online (Sandbox Code Playgroud)
常量'A'是类型int(由于历史原因,它int不是char,但是相同的参数将适用于任何一种方式).有没有隐式转换从int到char*,事实上,上述初始化是违反约束.这意味着编译器必须发出诊断(gcc确实),并且可能拒绝该程序(除非您使用gcc否则-pedantic-errors).一旦发出诊断,编译器就可以做任何喜欢的事情; 行为是不确定的(在这一点上存在一些语言 - 律师的分歧,但这并不重要).gcc选择将A from 的值转换int为char*(可能由于历史原因,返回到C的类型甚至比现在更少的类型),导致垃圾指针具有可能看起来像0x00000041或0x0000000000000041`的表示.
然后传递该垃圾指针printf,该指针尝试使用它来访问内存中该位置的字符串.随之而来的是欢闹.
要记住两件重要的事情:
如果您的编译器打印警告,请密切关注它们.gcc特别发出许多警告,恕我直言应该是致命的错误.永远不要忽略警告,除非您了解警告的含义,足以让您的知识超越编译器的作者.
数组和指针是非常不同的东西.C语言的几个规则似乎合谋使它看起来像是一样的.假设数组只不过是伪装的指针,你可以暂时逃脱,但这种假设最终会回来咬你.阅读comp.lang.c FAQ的第6部分; 它比我更好地解释了数组和指针之间的关系.
关于代码段#2:
该代码的工作原理是因为C99中的一个新功能,称为复合文字.你可以在几个地方阅读它们,包括GCC的文档, Mike Ash的文章,以及一些谷歌搜索.
从本质上讲,编译器创建的堆栈上的临时数组,并用3个字节填充它- ,0x48,0x69和0x00.该临时数组一旦创建,就会被衰减为指针并传递给该printf函数.关于复合文字的一个非常重要的事情是它们const不像大多数C字符串那样默认.
关于代码段#3:
你实际上根本没有创建一个数组 - 你在标量初始化器中投射第一个元素,在这种情况下是H,或者0x48是指针.您可以通过将%sprintf语句更改为a %p来为此看到,它为我提供了此输出:
0x48
因此,你必须非常小心你使用复合文字做什么 - 它们是一个强大的工具,但很容易用它们射击自己.