为什么解引用指向string(char数组)的指针会返回整个字符串而不是第一个字符?

时间:2016-08-25 21:30:22

标签: c arrays string pointers pointer-arithmetic

由于指向数组的指针指向数组的第一个元素(具有相同的地址),我不明白为什么会这样:

#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"
}

3 个答案:

答案 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};
                   ^

因此,1int转换为指向数组的指针 - 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}}进行的操作。