为什么取消引用指向字符串(字符数组)的指针 returns 整个字符串而不是第一个字符?
Why does dereferencing a pointer to string (char array) returns the whole string instead of the first character?
由于数组指针指向数组的第一个元素(具有相同的地址),我不明白为什么会这样:
#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"
}
*t
将获取第一个元素,然后加 1,并且由于 指针算法 这意味着,前进一个元素,这解释了为什么你得到第二个元素。
现在,在第一种情况下,您使用 %s
打印,它告诉我打印字符串(直到遇到 NULL 终止符),而在第二种情况下,您使用 %d
打印,只是一个数字.
如果你也想在第一种情况下体验与 %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
,因为解引用指向数组的第一个元素,即字符串的第一个字符,然后你向它添加一个,但是因为它是一个指针,它会根据指针 arithmetic 前进到下一个元素(因为我们做了 +1。如果我们做了 +2,它会前进 2 个元素,依此类推。) .
现在正如我上面所解释的,%s
将打印整个字符串,从 printf()
参数的起始指针开始,直到它到达字符串的 NULL 终止符。
因此,这将打印 "est text"。
第二个 printf()
遵循相同的理念,但它的参数前面有 *
运算符,这意味着给我指向的元素,即字符串的第二个字符。
因为我们使用 %c
,它只会打印那个字符,即 "e".
在 C 中,字符串只是 char
的数组,以 [=15=]
字符结尾。
当你这样做时:
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', '[=12=]'};
printf("%s\n", t + 1);
甚至,如果您不想修改字符串:
const char *t = "test text";
printf("%s\n", t + 1);
要打印单个字符,请使用 %c
(传入 char
,而不是指针,因此在您的代码中它将是 *(*t+1)
或者只是 t[1]
在我的示例中,这就是您使用 %d
).
所做的
在撰写此答案时,所有其他答案都不正确。此外,您的问题闻起来像 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.
要获取指向数组的指针,必须在数组(字符串文字!)上使用 "address-of" 运算符,以避免它衰减为指针:
char (*t)[] = &"test text";
现在 t
被正确初始化为指向 char
的(不可变)数组的指针。但是,在您的情况下,使用指向不正确类型的指针并不重要,因为这 2 个指针尽管类型不兼容,但指向相同的地址 - 只有一个指向字符数组,另一个指向第一个字符在那个字符数组中;因此观察到的行为是相同的。
当您取消引用 t
时,它是指向数组 char
的指针,您将获得数组 char
的定位器值(左值)。 char 数组的左值在正常情况下将衰减为指向第一个元素的指针,就像它们通常所做的那样,因此 *t + 1
现在将指向该数组中的第二个字符;然后 printf
ing 该值将打印从该指针 .
开始的以 0 结尾的字符串的内容
%s
的行为在 C11 (n1570) 中指定为
[%s
]
If no l
length modifier is present, the argument shall be a pointer to the initial
element of an array of character type. Characters from the array are
written up to (but not including) the terminating null character. [...] If the
precision is not specified or is greater than the size of the array, the array shall
contain a null character. [...]
(强调我的。)
关于你的第二次初始化:
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
当你取消引用这个值(它是一个指向字符数组的指针)时,你将得到一个从内存地址 1 开始的字符数组的左值。当你加 1 时,这个 array-of-char 左值将衰减为指向 char 的指针,结果您将得到 ((char*)1) + 1
这是指向 char
的指针值为 2
。可以从 GCC (5.4.0) 默认生成的警告中验证该值的类型:
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 };
虽然这可能甚至 少于 您想要的,并且生成的代码没有任何机会在 C89 或 C++ 编译器中编译。
由于数组指针指向数组的第一个元素(具有相同的地址),我不明白为什么会这样:
#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"
}
*t
将获取第一个元素,然后加 1,并且由于 指针算法 这意味着,前进一个元素,这解释了为什么你得到第二个元素。
现在,在第一种情况下,您使用 %s
打印,它告诉我打印字符串(直到遇到 NULL 终止符),而在第二种情况下,您使用 %d
打印,只是一个数字.
如果你也想在第一种情况下体验与 %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
,因为解引用指向数组的第一个元素,即字符串的第一个字符,然后你向它添加一个,但是因为它是一个指针,它会根据指针 arithmetic 前进到下一个元素(因为我们做了 +1。如果我们做了 +2,它会前进 2 个元素,依此类推。) .
现在正如我上面所解释的,%s
将打印整个字符串,从 printf()
参数的起始指针开始,直到它到达字符串的 NULL 终止符。
因此,这将打印 "est text"。
第二个 printf()
遵循相同的理念,但它的参数前面有 *
运算符,这意味着给我指向的元素,即字符串的第二个字符。
因为我们使用 %c
,它只会打印那个字符,即 "e".
在 C 中,字符串只是 char
的数组,以 [=15=]
字符结尾。
当你这样做时:
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', '[=12=]'};
printf("%s\n", t + 1);
甚至,如果您不想修改字符串:
const char *t = "test text";
printf("%s\n", t + 1);
要打印单个字符,请使用 %c
(传入 char
,而不是指针,因此在您的代码中它将是 *(*t+1)
或者只是 t[1]
在我的示例中,这就是您使用 %d
).
在撰写此答案时,所有其他答案都不正确。此外,您的问题闻起来像 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.
要获取指向数组的指针,必须在数组(字符串文字!)上使用 "address-of" 运算符,以避免它衰减为指针:
char (*t)[] = &"test text";
现在 t
被正确初始化为指向 char
的(不可变)数组的指针。但是,在您的情况下,使用指向不正确类型的指针并不重要,因为这 2 个指针尽管类型不兼容,但指向相同的地址 - 只有一个指向字符数组,另一个指向第一个字符在那个字符数组中;因此观察到的行为是相同的。
当您取消引用 t
时,它是指向数组 char
的指针,您将获得数组 char
的定位器值(左值)。 char 数组的左值在正常情况下将衰减为指向第一个元素的指针,就像它们通常所做的那样,因此 *t + 1
现在将指向该数组中的第二个字符;然后 printf
ing 该值将打印从该指针 .
%s
的行为在 C11 (n1570) 中指定为
[
%s
]If no
l
length modifier is present, the argument shall be a pointer to the initial element of an array of character type. Characters from the array are written up to (but not including) the terminating null character. [...] If the precision is not specified or is greater than the size of the array, the array shall contain a null character. [...]
(强调我的。)
关于你的第二次初始化:
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
当你取消引用这个值(它是一个指向字符数组的指针)时,你将得到一个从内存地址 1 开始的字符数组的左值。当你加 1 时,这个 array-of-char 左值将衰减为指向 char 的指针,结果您将得到 ((char*)1) + 1
这是指向 char
的指针值为 2
。可以从 GCC (5.4.0) 默认生成的警告中验证该值的类型:
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 };
虽然这可能甚至 少于 您想要的,并且生成的代码没有任何机会在 C89 或 C++ 编译器中编译。