我对 C 中的 (char*) 元素数组进行了三个循环。为什么第三个循环失败了?
I have three loops over an array of (char*) elements in C. Why does the third fail?
在尝试在 C 中单步执行字符串数组的方法时,我开发了以下小程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = {"father", "mother", "son", NULL};
string family2[4] = {"father", "mother", "son", NULL};
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
}
我的具体问题涉及循环 #3 的失败。当 运行 通过调试器时,循环 #1 和 #2 成功完成,但最后一个循环因未知原因失败。我不会在这里问这个问题,除非事实表明我对“&”运算符有一些严重的误解。
我的问题(和目前的理解)是这样的:
family2
是一个指针数组-字符。因此,当 s
设置为 family2[0]
时,我们有一个 (char*)
指向 "father"。因此,采用 &s
应该给我们相当于 family2
,指向预期指针衰减后 family2
的第一个元素。那么,为什么不,
*(&s + 1)
指向下一个元素,如预期的那样?
非常感谢,
生活危机
编辑——更新和经验教训:
以下列表总结了所有相关事实和解释,这些事实和解释解释了为什么第三个循环不像前两个循环那样工作。
s
是一个单独的变量,保存变量 family2[0]
的值的副本(指向字符的指针)。即,这两个等效值位于内存中的不同位置。
family2[0]
到 family2[3]
是内存的连续元素,并且 s
不存在于此 space 中,尽管它确实包含与存储的相同的值在 family2[0]
循环开始时。
- 前两个事实意味着
&s
和 &family2[0]
不相等。因此,向 &s
添加 1 将 return 指向 unknown/undefined 数据的指针,而向 &family2[0]
添加 1 将根据需要为您提供 &family2[1]
。
- 此外,第三个 for 循环中的更新步骤实际上不会导致 s 在每次迭代时在内存中向前推进。这是因为
&s
在我们循环的所有迭代中都是不变的。这就是观察到的无限循环的原因。
感谢大家的帮助!
生活危机
当您执行 s = *(&s + 1)
时,变量 s
是隐式作用域中的局部变量,仅包含循环。当您执行 &s
时,您将获得与任何数组无关的局部变量的地址。
与上一个循环不同的是s
是指向数组第一个元素的指针
再解释一下"graphically"你在最后一个循环中的内容类似于
+----+ +---+ +------------+
| &s | ---> | s | ---> | family2[0] |
+----+ +---+ +------------+
即&s
指向s
,s
指向family2[0]
。
当你&s + 1
时,你实际上得到了类似
的东西
+------------+
| family2[0] |
+------------+
^
|
+---+----
| s | ...
+---+----
^ ^
| |
&s &s + 1
图片很有帮助:
+----------+
| "father" |
+----------+ +----------+ +-------+ NULL
/-----------→1000 | "mother" | | "son" | ↑
+-----+ ↑ +----------+ +-------+ |
| s | ? | 2000 2500 |
+-----+ | ↑ ↑ |
6000 6008 +----------------+----------------+--------------+--------------+
| family2[0] | family2[1] | family2[2] | family2[3] |
+----------------+----------------+--------------+--------------+
5000 5008 5016 5024
( &s refers to 6000 )
( &s+1 refers to 6008 but )
( *(&s+1) invokes UB )
为简单起见,地址选择为随机整数
这里的问题是,虽然 s
和 family2[0]
都指向字符串文字 "father"
的相同基地址,但指针彼此不相关并且有自己不同的存储位置。 *(&s+1) != family2[1]
.
您在执行 *(&s + 1)
时点击了 UB,因为 &s + 1
是您不应该篡改的内存位置,即它不属于您创建的任何对象。你永远不知道那里存储了什么 => 未定义的行为。
感谢@2501指出几个错误!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = { "father", "mother", "son", NULL };
string family2[4] = { "father", "mother", "son", NULL };
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
/*for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
*/
for (int j = 0; j < 3; j++)
{
printf("%d ",family2[j]);
printf("%d\n", strlen(family2[j]));
}
printf("\n");
int i = 0;
for (string s = family2[i]; i != 3; s = (s + strlen(family2[i]) + 2),i++) {
printf("%d ",s);
printf("%s\n", s);
}
system("pause");
}
这是根据你的代码修改的例子,如果你运行它,你会发现point和family2的地址发生了变化,那么你就会明白循环#3的关系。
在尝试在 C 中单步执行字符串数组的方法时,我开发了以下小程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = {"father", "mother", "son", NULL};
string family2[4] = {"father", "mother", "son", NULL};
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
}
我的具体问题涉及循环 #3 的失败。当 运行 通过调试器时,循环 #1 和 #2 成功完成,但最后一个循环因未知原因失败。我不会在这里问这个问题,除非事实表明我对“&”运算符有一些严重的误解。
我的问题(和目前的理解)是这样的:
family2
是一个指针数组-字符。因此,当 s
设置为 family2[0]
时,我们有一个 (char*)
指向 "father"。因此,采用 &s
应该给我们相当于 family2
,指向预期指针衰减后 family2
的第一个元素。那么,为什么不,
*(&s + 1)
指向下一个元素,如预期的那样?
非常感谢,
生活危机
编辑——更新和经验教训:
以下列表总结了所有相关事实和解释,这些事实和解释解释了为什么第三个循环不像前两个循环那样工作。
s
是一个单独的变量,保存变量family2[0]
的值的副本(指向字符的指针)。即,这两个等效值位于内存中的不同位置。family2[0]
到family2[3]
是内存的连续元素,并且s
不存在于此 space 中,尽管它确实包含与存储的相同的值在family2[0]
循环开始时。- 前两个事实意味着
&s
和&family2[0]
不相等。因此,向&s
添加 1 将 return 指向 unknown/undefined 数据的指针,而向&family2[0]
添加 1 将根据需要为您提供&family2[1]
。 - 此外,第三个 for 循环中的更新步骤实际上不会导致 s 在每次迭代时在内存中向前推进。这是因为
&s
在我们循环的所有迭代中都是不变的。这就是观察到的无限循环的原因。
感谢大家的帮助!
生活危机
当您执行 s = *(&s + 1)
时,变量 s
是隐式作用域中的局部变量,仅包含循环。当您执行 &s
时,您将获得与任何数组无关的局部变量的地址。
与上一个循环不同的是s
是指向数组第一个元素的指针
再解释一下"graphically"你在最后一个循环中的内容类似于
+----+ +---+ +------------+ | &s | ---> | s | ---> | family2[0] | +----+ +---+ +------------+
即&s
指向s
,s
指向family2[0]
。
当你&s + 1
时,你实际上得到了类似
+------------+ | family2[0] | +------------+ ^ | +---+---- | s | ... +---+---- ^ ^ | | &s &s + 1
图片很有帮助:
+----------+
| "father" |
+----------+ +----------+ +-------+ NULL
/-----------→1000 | "mother" | | "son" | ↑
+-----+ ↑ +----------+ +-------+ |
| s | ? | 2000 2500 |
+-----+ | ↑ ↑ |
6000 6008 +----------------+----------------+--------------+--------------+
| family2[0] | family2[1] | family2[2] | family2[3] |
+----------------+----------------+--------------+--------------+
5000 5008 5016 5024
( &s refers to 6000 )
( &s+1 refers to 6008 but )
( *(&s+1) invokes UB )
为简单起见,地址选择为随机整数
这里的问题是,虽然 s
和 family2[0]
都指向字符串文字 "father"
的相同基地址,但指针彼此不相关并且有自己不同的存储位置。 *(&s+1) != family2[1]
.
您在执行 *(&s + 1)
时点击了 UB,因为 &s + 1
是您不应该篡改的内存位置,即它不属于您创建的任何对象。你永远不知道那里存储了什么 => 未定义的行为。
感谢@2501指出几个错误!
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef char* string;
int main() {
char *family1[4] = { "father", "mother", "son", NULL };
string family2[4] = { "father", "mother", "son", NULL };
/* Loop #1: Using a simple pointer to step through "family1". */
for (char **p = family1; *p != NULL; p++) {
printf("%s\n", *p);
}
putchar('\n');
/* Loop #2: Using the typedef for clarity and stepping through
* family2. */
for (string *s = family2; *s != NULL; s++) {
printf("%s\n", *s);
}
putchar('\n');
/* Loop #3: Again, we use the pointer, but with a unique increment
* step in our for loop. This fails to work. Why? */
/*for (string s = family2[0]; s != NULL; s = *(&s + 1)) {
printf("%s\n", s);
}
*/
for (int j = 0; j < 3; j++)
{
printf("%d ",family2[j]);
printf("%d\n", strlen(family2[j]));
}
printf("\n");
int i = 0;
for (string s = family2[i]; i != 3; s = (s + strlen(family2[i]) + 2),i++) {
printf("%d ",s);
printf("%s\n", s);
}
system("pause");
}
这是根据你的代码修改的例子,如果你运行它,你会发现point和family2的地址发生了变化,那么你就会明白循环#3的关系。