此代码在指针数组中返回错误的第一个指针

时间:2014-01-20 06:56:19

标签: c pointers coding-style programming-languages

这是我的代码

#include <stdio.h>

int main()
{
    char *fruit[] = {
        "Water",
        "banana",
        "pear",
        "apple",
        "coconut",
        "grape",
        "blueberry"
    };
    int x;
    int g;

    for(x=0;*(fruit+x)!='\0';x++)
    {
        for (g=0; *(*fruit)++; g++)
        {
            putchar(*(*(fruit+x)+g));
        }
        putchar('\n');
    }

    return(0);
}

这就是代码返回的内容......

aeaa
banana
pear
apple
coconut
grape
blueberry
Program ended with exit code: 0

我不明白我做错了什么...为什么第一点出错而其他指针显示出我的预期。

任何人都可以解释一下吗?我正在使用Xcode

3 个答案:

答案 0 :(得分:2)

要了解发生了什么,您应该在第一个for行放置断点,运行代码直至停止,然后逐步查看fruitx和放大器的值; g。如果你这样做,你会注意到一些奇怪的事情 - 在第一次围绕外环之后,fruit的第一个元素变为banana(其余条目保持不变,所以你现在有两个香蕉)。

立即行动,观看。如果你现在不能使用调试器停止并弄清楚。

那发生了什么?

首先让我们看一下代码输出的内容:

ae?aa (? is actually upside down on my run)
banana
etc.

这些角色哪里可以来自哪里?好吧,如果编译器将你的字符串打包到内存中,记住每个字符串以空字节结尾,我们将由?表示,然后将其打包到内存中:

Water?banana?pear?apple

等。你的程序打印出5个字符, Water 中的数字,它们是内存中的第2,第4,第6,第8和第10个......

现在我们知道我们看到了什么为什么我们看到了它?

让我们看看fruit,它是char *的一维数组 - 指向包含字符串中字符的内存的指针。如果我在运行代码时查看此数组中的前几个指针,它们是(实际值可能不同):

0x100000e24
0x100000e2a
0x100000e31

仅查看最后两位数字,并从十六进制转换,我们有36,42,49。第一个区别是6,即存储"Water"所需的字节数(包括尾随空字节),接下来是7,存储"banana"所需的字节数,因此它继续。

现在让我们看一下你的第一个for循环:

for(x=0; *(fruit+x) != '\0'; x++)

现在*(fruit+x)与编写fruit[x]相同,因此在循环的每次迭代中,您都会看到从{1}开始的fruit元素(索引0)。现在fruit包含char *类型的值,但您要将值与char进行比较 - 它们不是一回事!

当C构造一个带有未指定边界的数组([]声明中的空fruit)来自文字时,你在这里完成它不会在最后一个元素之后添加任何内容数组表示没有更多的元素。有两种常见的方法可以解决这个问题:您可以使用两次sizeof调用来计算元素数量(请参阅@Lundin的回答);或者您可以使用 sentinel - 否则将不会出现在数组中的值。对于指针值数组,标准的标记是NULL - 预先声明的指向任何内容的指针。因此,为了使代码的这部分工作,我们将代码更改为:

char *fruit[] =
{
  "Water",
  "banana",
  "pear",
  "apple",
  "coconut",
  "grape",
  "blueberry",
  NULL
};

int x;
int g;

for(x=0; *(fruit+x) != NULL; x++)

此循环现在将x设置为值06

现在让我们来看看你的内循环:

for (g=0; *(*fruit)++; g++)

为了减少围绕我们头脑的星星,让*first替换first[0]

for (g=0; *(fruit[0])++; g++)

所以*(fruit[0])++

  1. 获取数组的第一个元素(fruit[0])中的值,该值是指向字符串char *的第一个字符的指针("Water");
  2. 通过该指针(*(fruit[0])进行间接获取单个字符,即W;
  3. 隐式地将该字符与空字节进行比较 - for的测试部分;和
  4. 增加存储在数组第一个元素中的指针
  5. 因此,在此循环的第一次迭代之后,fruit数组包含(假设值如上):

    0x100000e25
    0x100000e2a
    0x100000e31
    

    现在,第一个元素已更改为指向a中的"Water"。当你正在阅读阵列进行打印时,你也在改变它 - 可能不是一个好主意。

    另请注意,此循环始终引用数组的第一个元素,它永远不会移过它,但是外循环的多次迭代发生 - 这个循环也没有单步执行fruit的元素 - 这样做你需要在某个地方引用x,你不在for本身,但你在身体里做:

    putchar(*(*(fruit+x)+g))
    
    使用索引重写的

    是:

    putchar(fruit[x][g])
    

    输出fruit的第x个元素的第g个字符。如果数组fruit未被修改,这是有意义的,但是我们刚刚确定fruit的第一个元素现在引用了a中的"Water"x&amp; g均为零,因此您可以按预期输出a而不是W

    现在考虑第二次迭代,for循环检查a并发现它不是空字节,因此fruit[0]递增以引用t中的"Water" {1}}和g会增加到1。现在,putchar会查找fruit的第一个元素,它引用"ter..."两次递增,g1,因此选择了第二个字符,这是e,即输出。

    在每次迭代中,您在fruit[0]中递增指针并递增g,因此每次递增2并且putchar输出第2,第4,第6,第8和内存中的第10个字符......当字符串互相跟随时,您会得到ae和来自"Water"的空字节以及来自a的{​​{1}} {1}}。

    当你的程序继续"banana"在内存中进行游戏,但fruit[0]使用putchar,因此引用x中未更改的指针以及其他字符串 by运气,打印正确。如果你没有将sentinel添加到数组中,外部循环将继续在内存中行进,直到找到零字节为止 - 因此你也可能在fruit[1]之后进行垃圾打印。

    那你怎么解决这个问题?

    嗯,你应该永远不会在第一个位置改变blueberry,你应该引用内部fruit[0]中的x来逐步完成数组。

    保持与原始代码的接近,解决方案是将<{1}}中存储的指针复制到局部变量中并递增以遍历字符串:

    for

    HTH

答案 1 :(得分:1)

您忘记了NULL终止您的阵列:

char *fruit[] = {
    "Water",
    "banana",
    "pear",
    "apple",
    "coconut",
    "grape",
    "blueberry",
    NULL
};

你被undefined behavior击中了。

然后只使用puts

for(x=0;fruit[x]!=NULL;x++)
   puts(fruit[x]);

请编译所有警告和调试信息(可能是gcc -Wall -g)和使用调试器(可能是gdb) - 按步骤运行代码step printdisplay一些相关变量(例如xfruit

答案 2 :(得分:0)

代码不必要地复杂化。有没有理由不能简单地这样做?

#include <stdio.h>

#define ARRAY_SIZE(arr) (sizeof(arr)/sizeof(*arr))

int main()
{
    const char *fruit[] = 
    {
        "Water",
        "banana",
        "pear",
        "apple",
        "coconut",
        "grape",
        "blueberry"
    };


    for(int i=0; i<ARRAY_SIZE(fruit); i++)
    {
        puts(fruit[i]);
    }

    return 0;
}