与char **混淆

时间:2015-07-03 22:42:18

标签: c arrays pointers char

如果我写

//case 1
char *animals[2] = {"cat", "dog"};
char **animal_ptr = animals; 
printf("%s\n", *(animal_ptr + 1)); //fine

而且,以不同的方式:

//case 2
char *animal = "cat";
char **animal_ptr = &animal;
 *(animal_ptr + 1) = "dog";
printf("%s\n", *(animal_ptr + 1)); //fine

所以,我对上面的两个例子感到困惑。

  1. 在案例1中,我理解animal_ptr是指向指针集合的指针,并且当指针保存地址时,我不需要添加&。但是在第2种情况下,即使&已经是指针,我也必须添加animal才能正常工作。为什么?

  2. 在这两种情况下,为什么通过另一个可接受的指针修改字符串文字?据我所知,当你 声明一个字符串,如char *x = "..etc";,它放在内存中无法修改的部分。那么为什么{1}}和animal都可以修改字符串呢?

  3. 为什么animal_ptr失败,程序停止,即使作业在2中工作?

  4. 案例2中的
    • 当我做strcpy(*(animal_ptr + 1), "bird")时,它工作正常,对我来说很有意义。
    • 当我printf("%s", *animal_ptr)时,它会停止。为什么?
  5. 谢谢,很抱歉很多问题。

7 个答案:

答案 0 :(得分:5)

带指针和字符串的东西是你必须跟踪它们的空间分配位置,以及它是否可写。您还必须了解使用strcpy复制字符串与重新排列指针之间的区别。

当您使用字符串常量如"cat""dog"时,编译器会自动为字符串分配空间,但字符串不可修改。 (也就是说,您无法使用strcpy在其上复制新字符串。)

在你的情况1中,你有一个"数组"两个字符串。有几种方法可以考虑这个问题。由于字符串是一个字符数组,并且您有两个字符串,因此您可以将其视为"二维数组"。这就是为什么animals有一个*和一对括号[],以及为什么animal_ptr有两个*&#39}的原因。或者,由于char *是我们通常在C中引用字符串的方式,因此您可以看到animals是一个包含两个字符串的数组。

检查所有内容的分配也很重要。编译器负责"cat""dog"。您将animals分配为大小为2的数组,以便进行处理。最后,animal_ptr设置为指向animals所在的位置,因此它也得到了适当的分配。 (但请注意,animalsanimal_ptr指的是相同的存储空间。)

然而,情况2的情况有所不同。首先只用一个指针"cat"指向一个字符串animal。再次指向一个带有指向指针的字符串animal_ptr。到目前为止,一切都还可以,但我们所拥有的相当于一个字符串的数组。它相当于

char *animals[1]={"cat"};
char **animal_ptr=animals;

所以当你后来说*(animal_ptr+1)="dog"时,你正在写一个"数组"的单元格。那不存在。你最终覆盖了内存的其他部分。有时你可以侥幸逃脱,有时它会使程序的其他部分行为错误,有时它会使你的程序崩溃。

如果不是*(animal_ptr+1)="dog"而是编写等效的

,可能会更容易看到这一点
animal_ptr[1] = "dog";

由于animal_ptr相当于1个元素的数组,因此唯一合法的下标是[0],而不是[1]

现在回答您的具体问题:

Q1。在这两个示例中,animal_ptr是指向指针的指针。在情况1中,animal是一个数组,在C中,您自动获得指向数组的第一个元素的指针,而不使用显式&。在第2种情况下,animal是一个简单的指针,因此您需要一个显式&来获取其地址,以生成animal_ptr所需的指针指针。

Q2。在任何情况下你都不修改任何字符串。你是正确的,字符串本身是不可写的,但是案例1 中的数组animals可写的,所以插入新的指针是没问题的。在奶牛回家之前,你可以说animals[0] = "chicken"animals[1] = "pig"之类的东西。在案例2中,您可以说animal = "chicken"animal_ptr[0] = "pig",因为第一个指针(animal)是可写的,但您无法修改animal_ptr[1],而不是因为它{&} #39;不可写,但因为它不存在。

Q3。你不能在这些例子中使用strcpy,因为它试图将新字符复制到你现有的一个字符串中,并且因为所有现有字符串都是编译器分配的而失败了不可写的。

如果您想查看strcpy的工作原理,则必须为strcpy分配一些可写存储空间以进行复制。你可以做点什么

char newarray[10];
animal_ptr[0] = newarray;
strcpy(animal_ptr[0], "bird");

animal_ptr[0] = malloc(10);
strcpy(animal_ptr[0], "bird");

Q4。当你printf("%s",*animal_ptr)相当于

printf("%s", animal_ptr[0]);

printf %s想要一个指针,然后你给它一个。但是,当您编写printf("%s", *animal)时,表达式*animal将获取animal指向的第一个字符,可能是'c'中的字母"cat"。然后,您正在使用该字符并要求printf将其作为字符串打印,并使用%s。但正如我们刚才所见,%s想要一个指针。所以它试图在内存中的地址99处打印字符串(因为'c'的ASCII值是99),并且崩溃了。

有关指针分配与strcpy的更多信息

关于C中字符串和指针的另一个问题是,它首先分配它们意味着什么令人困惑。 C没有真正的,#34;一流的&#34;内置字符串类型,以及字符串表示为字符/指针数组的方式使我们始终要记住指针指针指向的内容之间的区别< / em>的

为了使这一点非常明确,让我们暂时切换一下,并考虑指向整数的指针。假设我们有

int four = 4;
int five = 5;
int *intptr = &four;

所以我们有一个指针intptr,它指向的是值4(它恰好也是变量four的值)。如果我们再说

intptr = &five;

我们正在更改指针的值。它曾经指向four / 4,现在它指向five / 5。现在假设我们说

*intptr = 6;

在这种情况下,我们更改了指向的值intptr指向之前的位置(&five),但我们已将该位置的值从5更改为6。 (当然,现在如果我们说printf("%d", five),我们有点奇怪地得到6。)

现在,回到字符串。如果我有

char *animal = "cat";

然后我说

animal = "dog";

我已经改变了指针指向的位置。它曾经指向包含字符串"cat"的编译器分配的只读内存块,现在它指向一个包含字符串{{1}的不同的,编译器分配的只读内存块。 }。

现在假设我说

"dog"

在这种情况下,我没有更改指针strcpy(animal, "elephant"); ,我要求animal将新字符写入strcpy指向的位置。但是,请记住,animal当前指向包含字符串animal的编译器分配的只读内存块。由于它是只读的,因此尝试编写新字符的尝试失败了。即使由于这个原因它没有失败,我们也会遇到不同的问题,因为"dog"当然比"elephant"更大。

最重要的是,有两种完全不同的方式来分配&#34; C中的字符串:分配指针并调用"dog"。但他们真的完全不同。

您可能听说过&#34;您无法使用strcpy==<来比较字符串;你必须致电>&#34;。比较运算符比较指针(通常不是你想要的),而strcmp比较指向的字符。但是当涉及到赋值时,可以以这种方式执行:通过使用strcmp复制字符来重新分配指针,

但是如果你正在调用strcpy,你总是必须确保目标指针(a)确实指向某个地方,并指出它指向的内存区域(b)足够大( c)可写。目标指针通常指向您已分配的字符数组,或指向使用strcpy获得的动态内存区域。 (这是我在回答你的Q3时向你展示的内容。)但是目标不能是指向编译器分配字符串的指针。这很容易犯我的错误,但它并没有像你所见的那样奏效。

答案 1 :(得分:2)

仅仅因为您可以将数组视为指针并不意味着指针指向数组。在指针所指向的位置内存中的内容是错误的,这是现实世界中错误的主要来源之一。这就是为什么了解编译器为您分配的内容(全局或堆栈)以及您需要为自己分配的内容(通过动态分配,AKA malloc)非常重要。

所以,你的案例1代码:

/* case 1 line 1 */ char *animals[2] = { "cat","dog" }

编译器预先分配了两个四字节的内存块,并在第一个字符中存储了连续的字符&#39; c&#39; a&#39;&#39; t&#39;和0和&#39;,&#39; o&#39;,&#39; g&#39;,在第二个中为零。因为这些字符串是从字符串文字生成的编译器,所以它们(理论上至少)是只读的。试图改变&#39; c&#39;到了&#39;&#39;在你的代码中调用可怕的&#34;未定义的行为&#34;。如果您要求它报告可疑代码(通常称为警告),那么您在此处执行的初始化会向编译器发出抱怨。

编译器还预先分配了一个包含两个char *指针的数组,其中存储了预分配的&#34; cat&#34;的地址。字符串,以及预先分配的&#34; dog&#34;的地址串。这个阵列叫做动物。编译器知道它为数组分配了空间,一旦数组超出范围&#34;编译器将释放该空间。 animals数组有一个地址,可以存储在其他指针变量中。

/* case 1 line 2 */ char **animal_ptr=animals;

这里编译器预先为char **变量animal_ptr分配存储空间,然后用animals数组的地址初始化它。

我们开始看到作为数组的变量和作为指针的变量之间的区别。设置animal_ptr = animals是完全合法的,但设置animals = animal_ptr永远不合法。这里的一点是,数组可以用作它的等效类型的指针,但它不是指针。

/* case 1 line 3 */ printf("%s\n",*(animal_ptr+1));

指针添加定义为将指针的内容(在本例中为animals数组的地址)增加其指向的任何大小。在这种情况下,animal_ptr指向char *。然后,animal_ptr + 1将是指向动物阵列中第二个元素的地址的指针(又名&amp; animals [1])。解除引用*(animal_ptr + 1)生成一个char *指针,其值是&#34; dog&#34;的地址。串。 Printf的%s字符串解析器然后使用该地址打印字符串 dog

/* case 2 line 1 */ char *animal="cat";

编译器为字符串预先分配存储空间(&#39; c&#39;,&#39; a&#39;&#39; t&#39;,0)。编译器为指针动物预分配空间(sizeof char *),并用地址&#34; cat&#34;初始化它。字符串(此处会发生与之前相同的警告)。

/* case 2 line 2 */ char **animal_ptr=&animal;

编译器为指针animal_ptr预分配存储空间(sizeof char **)并使用动物指针的地址初始化它;

/* case 2 line 3 */  *(animal_ptr+1)="dog";

首先,编译器为&#34; dog&#34;预先分配空间。字符串,然后它尝试存储该分配块的地址......某处。

这是主要错误。 animal_ptr保存了动物的地址,但是在该地址分配的存储空间仅足以容纳一个指针(编译器在第1行中预分配)。当你在这里执行animal_ptr + 1时,你已经将指针移动到编译器为动物分配的空间之外。因此,您(可能)具有有效的内存地址,但它不会指向已知分配的内存中的某个位置。这是未定义的行为,并且可以预测解除引用此内存(在此处写入或从下一行读取)的结果。当然,您只是存储了&#34; dog&#34;的地址。在发生在动物指针上的任何事情之上的字符串。

/* case 2 line 4 */ printf("%s\n",*(animal_ptr+1)); //fine

嗯,你可以说它很好,但它确实不是。您再次取消引用存储在未分配内存中的指针。如果你运气好的话。如果你运气不好,你就会损坏你的筹码,当你继续前进的时候会发生意想不到的事情。这种事情正是特权漏洞所依据的那种错误。

回答您的具体问题:

  1. 指针只是内存地址,但编译器会关注它认为指向的内容。在案例2中,动物具有类型#char;&#39;和animal_ptr有类型&#39; char **&#39;。 &amp; animal有类型&#39; char **&#39;所以编译器接受了。

  2. 您的代码中没有任何内容试图修改&#34; dog&#34;和&#34; cat&#34;字符串。即使它们有,它仍然可以工作,因为通过强制非const指针修改const变量是未定义的行为。在加载器将字符串放在可写内存中的系统上,它可能会起作用。在将字符串放在只读存储器中的系统上,您将收到内存错误(例如,分段错误)。

  3. 嗯,这取决于strcpy的放置位置。我猜你的意思是代替案例2第3行?在这种情况下,*(animal_ptr + 1)是一个未初始化的指针,所以当strcpy尝试复制谁知道它在哪里尝试写字符串时。

  4. *动物属于char类型。 %s期待某种类型的char *。当printf尝试取消引用传入的值时,它不是一个有效的指针,因此它会走到谁知道在哪里尝试读取字符串。

答案 2 :(得分:1)

在两种情况下animal_ptr都是双指针:指向另一个指针的指针。在第一种情况下,它专门指向数组中的第一个元素,在情况2中指向唯一一个。

您可以将指针变量视为具有“级别”,其中lv2指针是指向另一个指针的指针,而lv3指向lv2,依此类推。声明变量时,每个*[]“增加”您的级别1.在分配值时,*访问指针内的信息,从而“降低”某个级别,& 1}}要求指针在内存中的位置,因此“增加”它。

P.S:在案例2中,你实际上搞得很糟糕!你正在写animal_ptr+1所指向的位置,这个位置可能被另一个变量(在大量时间弄乱你的代码)用在该函数的堆栈中,或者根本不是堆栈的一部分,因此得到了一个段错误!

编辑:更好地回答您的问题:

Q(1):动物是一个lv1指针,因此&amp; animal是一个lv2指针,与你正在分配它的char ** animal_ptr相同。

Q(2):更改指针变量的值与直接修改指向的位置值之间存在很大差异。即使您可以使用它们中的任何一个获得类似的结果,具体取决于您的代码。

问(3):"dog"是一个包含4个字符('d','o','g','\0')的数组,如果是1,您尝试将其替换为5 "bird"的数组。在案例2中,animal_ptr+1指向上帝知道预期在哪里发生段错误。

问(4):如果你理解我糟糕的关卡解释,你现在应该明白*动物是一个字符(动物字符串的第一个字母),而不是字符指针(字符串),因此不能以%s格式打印。

答案 3 :(得分:1)

在你的第一种情况下,你已经声明了一个长度为2的指针数组。你的作业** animal_ptr =动物在第一种情况下不需要和动物,因为在C中,指向数组的变量是指针

你的第二个案件一点也不好。当您进行赋值** animal_ptr =&amp; animal时,您可以有效地将** animal_ptr视为指向长度为1的指针数组的指针。因此,您的赋值*(animal_ptr + 1)=“dog”会尝试分配指向数组“dog”的指针指向该数组中不存在的第二个位置。这可能看似成功(即您的程序可能没有崩溃),但是通过对内存中尚未分配的位置进行分配,您实际上已经通过此操作损坏了内存。

在你的Q3中,你正在通过现在尝试将数组“bird”复制到animal_ptr + 1指向的内存中来复合案例2中的错误。我们已经知道animal_ptr + 1是一个未分配的位置,使用起来不安全。但另外它指向的位置(由于你的错误分配)是另一个字符串文字。

至于Q4。在内存损坏后没有什么是安全的。

答案 4 :(得分:1)

//case 1
char *animals[2]={"cat","dog"};
char **animal_ptr=animals; 
printf("%s\n",*(animal_ptr+1));      //fine
printf("%s\n",  animal_ptr[1]));     //equivalent
printf("%d\n", &animals == animals); // prints 1 i.e. true


//case 2
char *animal="cat";
char **animal_ptr=&animal;
 *(animal_ptr+1)="dog";
printf("%s\n",*(animal_ptr+1)); // fine
printf("%s\n", animal);         // prints cat

Q(1)

案例1:

  • animals是指向char种类
  • 的2元素指针数组
  • 特别是animals本身就是一个指针(指向第一个元素,它是指向char的指针)
  • animal_ptr是指向char类型
  • 的指针 类型animals
  • char*被分配到*animal_ptr类型的char*
  • 注意animals&animals保持相同的地址 - 它们具有相同的值,但属于不同的类型(请参阅here进行说明)

案例2:

  • animal是指向char类型的指针(使用"cat"初始化字符数组)
  • 特别是animal是指针(指向第一个元素,即字符'c'
  • animal_ptr是指向char类型
  • 的指针 类型*animal_ptr
  • char*被分配了指针animal地址,其类型为char*再次阅读animal指向char&animal指向char*
  • 我的作业并不清楚,你基本上是在定义一个新的字符串"dog"
  • 请注意,您仍然可以打印"cat"

Q(2)

我看不到你修改字符串的位置: - (

  • *animal_ptr指向cat字符串
  • *animal_ptr+1拥有另一个地址,并指向"dog"字符串

将它们打印出来,你会看到。

Q(3)

我认为你没有重新分配,而是从该地址定义一个新字符串。该字符串的长度为3,您要复制长度为4的字符串。因此失败了。

Q(4)

printf("%s", *animal)更改为printf("%c", *animal),因为您打印的是字符,而不是字符串。

答案 5 :(得分:-1)

类型char * []可以在这样的赋值中衰减为char **,因此您不需要那里的address-of运算符。在第二种情况下,你只需要一个char *,你需要获取它的地址才能得到一个char **。

如果文字不是常量,则文字不会放入某些只读部分。在这种情况下,每次调用函数时都可能在堆栈上创建它们。

如果某些工作正常,则并不意味着代码是正确的。在第二种情况下,您没有在第一种情况下分配第二个char *,因此增加animal_ptr并取消引用它是不合法的并且具有未定义的行为。你之后做的任何事情都可能有效或崩溃或做任何事情。

答案 6 :(得分:-1)

  1. 在C中,可以将数组视为不可指定的指针。从数组到指针的转换是自动的。所以你总是可以写这个

    int array[5];
    int *ptr = array; 
    
  2. 你看到的是一个指向指针的指针。它将地址存储到变量中。没有什么能阻止我们这样做

        int **array[5];
        int ***ptr = array; 
    

    并且更深入地指向指针。

    1. 变量声明中没有const,因此数据是可写的。

    2. 和4.第二个代码片段非常混乱。正如其他人所说,它会破坏记忆。你所做的是获取指向字符串的指针地址,移动一个指针并将这个未准备好的内存指针保存到&#34; dog&#34;串。正如其他人所说,如果内存被破坏,就无法确定,即使是正确的程序执行。