如果我写
//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中,我理解animal_ptr
是指向指针集合的指针,并且当指针保存地址时,我不需要添加&
。但是在第2种情况下,即使&
已经是指针,我也必须添加animal
才能正常工作。为什么?
在这两种情况下,为什么通过另一个可接受的指针修改字符串文字?据我所知,当你
声明一个字符串,如char *x = "..etc";
,它放在内存中无法修改的部分。那么为什么{1}}和animal
都可以修改字符串呢?
为什么animal_ptr
失败,程序停止,即使作业在2中工作?
:
strcpy(*(animal_ptr + 1), "bird")
时,它工作正常,对我来说很有意义。printf("%s", *animal_ptr)
时,它会停止。为什么?谢谢,很抱歉很多问题。
答案 0 :(得分:5)
带指针和字符串的东西是你必须跟踪它们的空间分配位置,以及它是否可写。您还必须了解使用strcpy
复制字符串与重新排列指针之间的区别。
当您使用字符串常量如"cat"
和"dog"
时,编译器会自动为字符串分配空间,但字符串不可修改。 (也就是说,您无法使用strcpy
在其上复制新字符串。)
在你的情况1中,你有一个"数组"两个字符串。有几种方法可以考虑这个问题。由于字符串是一个字符数组,并且您有两个字符串,因此您可以将其视为"二维数组"。这就是为什么animals
有一个*
和一对括号[]
,以及为什么animal_ptr
有两个*
&#39}的原因。或者,由于char *
是我们通常在C中引用字符串的方式,因此您可以看到animals
是一个包含两个字符串的数组。
检查所有内容的分配也很重要。编译器负责"cat"
和"dog"
。您将animals
分配为大小为2的数组,以便进行处理。最后,animal_ptr
设置为指向animals
所在的位置,因此它也得到了适当的分配。 (但请注意,animals
和animal_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
嗯,你可以说它很好,但它确实不是。您再次取消引用存储在未分配内存中的指针。如果你运气好的话。如果你运气不好,你就会损坏你的筹码,当你继续前进的时候会发生意想不到的事情。这种事情正是特权漏洞所依据的那种错误。
回答您的具体问题:
指针只是内存地址,但编译器会关注它认为指向的内容。在案例2中,动物具有类型#char;&#39;和animal_ptr有类型&#39; char **&#39;。 &amp; animal有类型&#39; char **&#39;所以编译器接受了。
您的代码中没有任何内容试图修改&#34; dog&#34;和&#34; cat&#34;字符串。即使它们有,它仍然可以工作,因为通过强制非const指针修改const变量是未定义的行为。在加载器将字符串放在可写内存中的系统上,它可能会起作用。在将字符串放在只读存储器中的系统上,您将收到内存错误(例如,分段错误)。
嗯,这取决于strcpy的放置位置。我猜你的意思是代替案例2第3行?在这种情况下,*(animal_ptr + 1)是一个未初始化的指针,所以当strcpy尝试复制谁知道它在哪里尝试写字符串时。
*动物属于char类型。 %s期待某种类型的char *。当printf尝试取消引用传入的值时,它不是一个有效的指针,因此它会走到谁知道在哪里尝试读取字符串。
答案 2 :(得分:1)
在两种情况下animal_ptr
都是双指针:指向另一个指针的指针。在第一种情况下,它专门指向数组中的第一个元素,在情况2中指向唯一一个。
您可以将指针变量视为具有“级别”,其中lv2指针是指向另一个指针的指针,而lv3指向lv2,依此类推。声明变量时,每个*
和[]
“增加”您的级别1.在分配值时,*
访问指针内的信息,从而“降低”某个级别,&
1}}要求指针在内存中的位置,因此“增加”它。
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
案例1:
animals
是指向char
种类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"
我看不到你修改字符串的位置: - (
*animal_ptr
指向cat
字符串*animal_ptr+1
拥有另一个地址,并指向"dog"
字符串将它们打印出来,你会看到。
我认为你没有重新分配,而是从该地址定义一个新字符串。该字符串的长度为3,您要复制长度为4的字符串。因此失败了。
将printf("%s", *animal)
更改为printf("%c", *animal)
,因为您打印的是字符,而不是字符串。
答案 5 :(得分:-1)
类型char * []可以在这样的赋值中衰减为char **,因此您不需要那里的address-of运算符。在第二种情况下,你只需要一个char *,你需要获取它的地址才能得到一个char **。
如果文字不是常量,则文字不会放入某些只读部分。在这种情况下,每次调用函数时都可能在堆栈上创建它们。
如果某些工作正常,则并不意味着代码是正确的。在第二种情况下,您没有在第一种情况下分配第二个char *,因此增加animal_ptr并取消引用它是不合法的并且具有未定义的行为。你之后做的任何事情都可能有效或崩溃或做任何事情。
答案 6 :(得分:-1)
在C中,可以将数组视为不可指定的指针。从数组到指针的转换是自动的。所以你总是可以写这个
int array[5];
int *ptr = array;
你看到的是一个指向指针的指针。它将地址存储到变量中。没有什么能阻止我们这样做
int **array[5];
int ***ptr = array;
并且更深入地指向指针。
变量声明中没有const,因此数据是可写的。
和4.第二个代码片段非常混乱。正如其他人所说,它会破坏记忆。你所做的是获取指向字符串的指针地址,移动一个指针并将这个未准备好的内存指针保存到&#34; dog&#34;串。正如其他人所说,如果内存被破坏,就无法确定,即使是正确的程序执行。