我对 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) 指向下一个元素,如预期的那样?

非常感谢,
生活危机


编辑——更新和经验教训:

以下列表总结了所有相关事实和解释,这些事实和解释解释了为什么第三个循环不像前两个循环那样工作。

  1. s 是一个单独的变量,保存变量 family2[0] 的值的副本(指向字符的指针)。即,这两个等效值位于内存中的不同位置。
  2. family2[0]family2[3] 是内存的连续元素,并且 s 不存在于此 space 中,尽管它确实包含与存储的相同的值在 family2[0] 循环开始时。
  3. 前两个事实意味着 &s&family2[0] 不相等。因此,向 &s 添加 1 将 return 指向 unknown/undefined 数据的指针,而向 &family2[0] 添加 1 将根据需要为您提供 &family2[1]
  4. 此外,第三个 for 循环中的更新步骤实际上不会导致 s 在每次迭代时在内存中向前推进。这是因为 &s 在我们循环的所有迭代中都是不变的。这就是观察到的无限循环的原因。

感谢大家的帮助!
生活危机

当您执行 s = *(&s + 1) 时,变量 s 是隐式作用域中的局部变量,仅包含循环。当您执行 &s 时,您将获得与任何数组无关的局部变量的地址。

与上一个循环不同的是s是指向数组第一个元素的指针


再解释一下"graphically"你在最后一个循环中的内容类似于

+----+      +---+      +------------+
| &s | ---> | s | ---> | family2[0] |
+----+      +---+      +------------+

&s指向ss指向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    )

为简单起见,地址选择为随机整数


这里的问题是,虽然 sfamily2[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的关系。