由于指向数组的指针指向数组的第一个元素(具有相同的地址),我不明白为什么会这样:
#include <stdio.h>
int main(void) {
char (*t)[] = {"test text"};
printf("%s\n", *t + 1); // prints "est text"
}
此外,为什么以下代码会打印2
呢?
#include <stdio.h>
int main(void) {
char (*t)[] = {1, 2, 3, 4, 5};
printf("%d\n", *t + 1); // prints "2"
}
答案 0 :(得分:7)
撰写此答案时的所有其他答案都不正确。此外,你的问题有点像an XY problem,因为你最想要的结构可能并不是你想要的。你真正想要做的只是:
char *t = "test text";
printf("%s\n", t); // prints "test text"
或
printf("%c\n", t[1]); // prints "e", the 2nd character in the string.
但既然你想了解为什么会发生这些事情,以及所有其他解释都是错误的,那就是:
您的声明将t
声明为指向char:
cdecl> explain char (*t)[];
declare t as pointer to array of char
不是其他人建议的指针数组。此外,*t
的类型不完整,因此您无法使用其大小:
sizeof *t;
将导致
error: invalid application of ‘sizeof’ to incomplete type ‘char[]’
sizeof *t;
在编译时。
现在,当您尝试使用
进行初始化时 char (*t)[] = {"test text"};
它会发出警告,因为虽然"test text"
是(常量)char
的数组,但它会衰减到指针char
。另外,那里的牙套没用;上面的摘录等同于写作:
char (*t)[] = "test text";
与
不同int a = 42;
和
int a = {42};
是同义词。这是C。
要获取指向数组的指针,必须使用&#34; address-of&#34;数组上的运算符(字符串文字!),以避免它衰减到指针:
char (*t)[] = &"test text";
现在t
被正确初始化为指向char
的(不可变)数组的指针。但是在你的情况下,使用指向不正确类型的指针并不重要,因为2指针尽管是不兼容的类型,却指向同样相同的地址 - 只有一个指向char-array,另一个指向char数组中的第一个字符;因此观察到的行为是相同的。
当您取消引用t
(指向数组的指针 - char
)时,您将获得array-of - char
的定位符值(左值)。然后,正常情况下,一个字符数组的左值将衰减到指向第一个元素的指针,就像它们通常那样,因此*t + 1
现在将指向该数组中的第二个字符;然后printf
该值将从该指针开始打印以0结尾的字符串的内容。
%s
的行为在C11(n1570)中指定为
[
%s
]如果不存在
l
长度修饰符,则参数应为指向初始值的指针 字符数组的元素。 数组中的字符是 写入(但不包括)终止空字符。 [...] 如果 精度未指定或大于数组的大小,数组应 包含空字符。 [...]
(强调我的。)
至于你的第二次初始化:
char (*t2)[] = {1, 2, 3, 4, 5};
如果使用最新版本的GCC编译它,默认情况下会收到很多警告,首先:
test.c:10:19: warning: initialization makes pointer from integer without a cast [-Wint-conversion]
char (*t2)[] = {1, 2, 3, 4, 5};
^
因此,1
从int
转换为指向数组的指针 - char
,而不进行任何转换。
然后,在剩下的值中,编译器会抱怨:
y.c:10:19: note: (near initialization for ‘t2’)
y.c:10:21: warning: excess elements in scalar initializer
char (*t2)[] = {1, 2, 3, 4, 5};
^
也就是说,在你的情况下,2,3,4和5被默默地忽略了。
因此,该指针的值现在为1,例如在x86平面内存模型上,它将指向内存位置1(虽然这是自然实现的定义):
printf("%p\n", (void*)t2);
打印(双重实现定义)
0x1
当你取消引用这个值(这是一个指向char数组的指针)时,你将得到一个从内存地址1开始的char-array的左值。当你加1时,这个 array-of-char 左值将衰减为指向char的指针,因此您将得到((char*)1) + 1
,它是指向char
的指针,其值为{{1 }}。可以从GCC(5.4.0)默认生成的警告中验证该值的类型:
2
参数类型为y.c:5:10: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]
printf("%d\n",*t2+1); //prints "2"
^
。
现在,您将char *
作为参数传递给(char*)2
,并使用printf
进行转换,该%d
需要int
。这有不明确的行为;在您的情况下,(char*)2
的字节模式被充分混淆地解释为2
,因此它被打印出来。
现在有人意识到打印的价值与原始初始化程序中的2
无关:
#include <stdio.h>
int main(void) {
char (*t2)[] = {1, 42};
printf("%d\n", *t2 + 1);
}
仍会打印2
,而不是42
。 QED。
或者对于两个初始化,您可以使用C99复合文字进行初始化:
// Warning: this code is super *evil*
char (*t)[] = &(char []) { "test text" };
char (*t2)[] = &(char []) { 1, 2, 3, 4, 5 };
虽然这可能是你想要的 less ,但结果代码没有任何机会在C89或C ++编译器中进行编译。
答案 1 :(得分:-2)
*t
将获取第一个元素,然后添加1,并且由于指针算术这意味着,前进一个元素,这就解释了为什么你得到第二个元素。
现在,在第一种情况下,您使用%s
打印,其中打印字符串打印(直到NULL终止符合为止),而在第二种情况下,使用%d
打印,只打印一个数字。< / p>
如果您希望在第一种情况下使用%c
体验等效行为,当然还需要演员。
顺便说一句,正如已经提到的那样,通常不会这样做:
char (*t)[] = {"test text"};
创建一个指针数组,第一个元素是字符串,它应该引发警告:
C02QT2UBFVH6-lm:~ gsamaras$ gcc -Wall main.c
main.c:4:18: warning: incompatible pointer types initializing 'char (*)[]' with an expression of type 'char [10]'
[-Wincompatible-pointer-types]
char (*t)[] = {"test text"};
^~~~~~~~~~~
正如奥拉夫所说,这:
char (*t)[] = {&"test text"};
将使警告消失,因为您现在正在将字符串的地址分配给指针。
现在试着想一想这会打印什么:
include <stdio.h>
int main(void) {
char (*t)[] = {&"test text"};
printf("%s\n", *t + 1);
printf("%c\n", *(*t + 1));
return 0;
}
第一个将采取您所期望的,而第二个需要额外的解除引用,以实际获得角色。
但这样的事情很平常:
char t[] = "test text";
当然还有其他方法。
那么,在这种情况下,请问这个程序会打印什么?
#include <stdio.h>
int main(void) {
char t[] = "test text";
printf("%s\n", t + 1);
printf("%c\n", *(t + 1));
return 0;
}
第一个print()
将取t
,这是因为解除引用指向数组第一个元素的点,即字符串的第一个字符,然后你添加一个,但因为它& #39; sa指针,它由于指针算术而前进到下一个元素(因为我们做+1。如果我们做+2,它将推进2个元素,依此类推......)。
正如我上面解释的那样,%s
将从printf()
参数的起始指针打印整个字符串,直到它到达字符串的NULL终止符。
因此,这将打印&#34; est text&#34;。
第二个printf()
遵循相同的精确哲学,但其参数前面是*
运算符,这意味着给我指向的元素,即字符串的第二个字符。
由于我们使用%c
,它只会打印该字符,即&#34; e&#34;。
答案 2 :(得分:-4)
在C中,字符串只是由char
字符终止的\0
数组。
当你这样做时:
char (*t)[] = {"test text"};
您正在创建一个指针数组,并使用"test text"
填充第一个元素,这是一个指向编译器将为您创建的零终止char
数组的指针。当您取消引用t
时,您会得到一个指向该字符串的指针,然后您添加1,使其指向第二个字符,%s
将所有内容打印到零终止符。
你也可以写:
char t[] = "test text";
printf("%s\n", t + 1);
或者:
char t[] = {'t', 'e', 's', 't', ' ', 't', 'e', 'x', 't', '\0'};
printf("%s\n", t + 1);
甚至,如果您不想修改字符串:
const char *t = "test text";
printf("%s\n", t + 1);
要打印单个字符,请使用%c
(传入char
,而不是指针,因此代码中的*(*t+1)
或我的t[1]
只有%d
示例,这是您使用{{1}}进行的操作。