【问题标题】:I have three loops over an array of (char*) elements in C. Why does the third fail?我在 C 中的 (char*) 元素数组上有三个循环。为什么第三个循环失败?
【发布时间】:2017-01-10 14:00:18
【问题描述】:

在尝试用 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);
  }
}

我的具体问题涉及 Loop #3 的失败。通过调试器运行时,循环 #1 和 #2 成功完成,但最后一个循环由于未知原因而失败。我不会在这里问这个问题,除非这表明我对“&”运算符有一些严重的误解。

我的问题(和目前的理解)是这样的:

family2 是一个指向字符的数组。因此,当s 设置为family2[0] 时,我们有一个(char*) 指向“父亲”。因此,取&amp;s 应该给我们等价于family2,指向预期指针衰减后family2 的第一个元素。那为什么不呢, *(&amp;s + 1) 指向下一个元素,如预期的那样?

非常感谢,
生命危机


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

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

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

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

【问题讨论】:

  • 什么是string? C 没有字符串类型。
  • 注意代码块顶部的 typedef!谢谢!
  • 啊,是的!所以另一个警告适用:永远不会 typedef 一个指针!它混淆了代码,向命名空间发送垃圾邮件,并且使限定符正确的代码变得很困难,如果不是不可能的话。
  • typedef 指针永远不是一个好主意。阅读this
  • 根据您的编辑,您似乎对答案感到满意。如果是这样,你应该accept one of them

标签: c arrays pointers memory-address


【解决方案1】:

当您执行s = *(&amp;s + 1) 时,变量s 是隐式范围内的局部变量,仅包含循环。当您执行&amp;s 时,您将获得该局部变量的地址,该地址与任何数组都无关。

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


为了更“图形化”地解释一下,你在最后一个循环中的内容类似于

+----+ +---+ +------------+ | &s | ---> |小号 | ---> |家庭2[0] | +----+ +---+ +------------+

&amp;s指向ss指向family2[0]

当您执行&amp;s + 1 时,您实际上拥有类似

+------------+ |家庭2[0] | +------------+ ^ | +---+---- |小号 | ... +---+---- ^ ^ | | &s &s + 1

【讨论】:

  • 我同意s 只能在循环内本地访问。但是,&amp;s 不应该实际上是指向family2 的第一个元素的(char**),它本身就是(char*)
  • @lifecrisis 不,&amp;s 是指向s 的指针,仅此而已。
  • 我想知道为什么这个完全正确的答案被否决了。
  • @MichaelWalz Obscene 如果你问我的话。
  • 嘿@Someprogrammerdude,看看上面的摘要编辑,我相信我清楚地总结了这次讨论的结果。让我知道它是否有问题(更有经验的眼睛会很有帮助!)。
【解决方案2】:

图片很有帮助:

            +----------+
            | "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" 的相同基地址,但这些指针彼此不相关,并且它们有自己不同的内存位置被存储。 *(&amp;s+1) != family2[1].

当您执行*(&amp;s + 1) 时,您点击了UB,因为&amp;s + 1 是您不应该篡改的内存位置,即它不属于您创建的任何对象。你永远不知道那里存储了什么 => 未定义的行为。

感谢@2501指出几个错误!

【讨论】:

  • 在你回答的最后一部分你是说&amp;s + 1 != family2[1]吗?两种说法都是正确的,但我只是想知道在这种情况下哪个更重要......
  • 这也是正确的,但类型不同。 s+1 != family2[1] 是两个 char*s 的比较,而 &amp;s+1 != family2[1]char**char* 的比较,这没有任何意义。
  • 不清楚你在这里代表的是哪个例子。不管怎样,这是错误的。 &s+1 不指向任何东西。它甚至不是一个有效的对象。
  • 这似乎是第三个例子。在这种情况下,s+1 指向“父亲”的第二个字符,而不是 1007。
  • family2[0] 和 s 也在使用误导性箭头。它们指向同一个地址,1000,但 s 指向列的左侧而不是地址。
【解决方案3】:
#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");

}

这是一个从你的代码修改的例子,如果你运行它,你会发现点的地址和family2发生了变化,那么你就会明白循环#3的关系了。

【讨论】:

  • s = (s + strlen(family2[i]) + 2)s 移出字符串范围
猜你喜欢
  • 2011-06-18
  • 2018-06-11
  • 2021-06-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-24
  • 1970-01-01
  • 2022-06-12
相关资源
最近更新 更多