理解C指针、数组和负索引

9

我正在尝试学习C语言中的指针,并且为此目的参加了一次测试。以下是问题:

#include <stdio.h>

char *c[] = {"GeksQuiz", "MCQ", "TEST", "QUIZ"};
char **cp[] = {c+3, c+2, c+1, c};
char ***cpp = cp;

int main()
{
    printf("%s ", **++cpp);
    printf("%s ", *--*++cpp+3);
    printf("%s ", *cpp[-2]+3);
    printf("%s ", cpp[-1][-1]+1);
    return 0;
}

这行代码的结果:

 printf("%s ", *cpp[-2]+3);

这段内容有些让人困惑,但是让我逐步解释一下我所理解的。

  • char *c[] - 是一个指向char的指针数组。
  • char **cp[] - 是一个指向指向char的指针的指针数组(我认为这是*c[]的反向包装)。
  • char ***cpp - 是一个指向指向char的指针的指针的指针(我认为这是**cp[]的包装,可用于就地修改)。

**++cpp - 由于cpp指向cp,那么++cpp将指向cp+1,即c+2,因此双重引用将打印出TEST

*--*++cpp+3 - 由于现在cpp指向cp+1,那么++cpp将指向cp+2,即c+1,接下来的操作--将给我们c的指针,因此最后的引用将打印出sQuiz

这里有些困惑:

cpp[-2] - 由于现在cpp指向cp+2,我可以确认这一点。

printf("%p\n", cpp); // 0x601090   
printf("%p\n", cp+2); // 0x601090

这里我打印了指向 c 的指针的地址。

printf("c - %p\n", c); // c - 0x601060
printf("c+1 - %p\n", c+1); // c+1 - 0x601068
printf("c+2 - %p\n", c+2); // c+2 - 0x601070
printf("c+3 - %p\n", c+3); // c+3 - 0x601078

所以,当我像这样去解引用 *(cpp[0]) 或者 **cpp 时,我期望得到的值是 c+1 中的 MCQ

printf("%p\n", &*(cpp[0])); // 0x601068

但是当我说*(cpp[-2])时,我得到了QUIZ,但我更期望得到一些垃圾值。

所以我的问题是:

  1. How the magic with *--*++cpp+3 works, I mean what is modified by the -- part that allows me to get MCQ instead of TEST when I dereference like this **cpp, I assume that this pointer *++cpp+3 preserves the state after the -- is applied, but cannot imagine yet how it works.

  2. Why the following works the way it works (the cpp[-2] part):

    printf("%p\n", &*cpp[1]); // 0x601060 -> c
    printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
    printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
    printf("%p", &*(cpp[-2])); // 0x601078 -> c+3
    

看起来顺序相反了,我可以接受&*(cpp[0])指向c+1,但我希望&*cpp[1]指向c+2&*(cpp[-1])指向c。 我在这个问题中找到了答案:Are negative array indexes allowed in C?

  1. 显然我混淆了很多东西,并且可能称某些不是指针的东西为指针,我想掌握指针的概念,所以如果有人能告诉我哪里错了,我会很高兴。

12
如果有人认为只有通过理解这样差劲的编程实践才能学会一门语言,那么他们是错的。放弃他们。 - R Sahu
3
你可能忽略了中括号可以被视为运算符,因此cpp[n]这个术语实际上与*(cpp + n)相同。 因此,使用负值作为n是可以接受的,但在实践中很少使用。 因此,负下标只是从指针cpp向后移动。 - bruceg
4
@RSahu 真的,我无法忍受老师选择出这种"陷阱"问题。这是可怕的编码实践,任何人都不应该遇到这种情况。如果这种问题进入了生产代码,就意味着公司没有实施最基本的代码审查实践。 - Christian Gibbons
2
我意识到 *--*++cpp+3 不是正确的写法,但那些棘手的测验问题仍然帮助我提高了对指针的理解。别担心,你不会看到我的 C 代码,我只是学习指针并离开。 - MisterAlejandro
3
@MisterAlejandro - 我们的意思是,学习 *--*++cpp+3 不重要,因为它从未被使用过。我不知道它是做什么的,但我并不在意(查看我的灰色胡须可以看出我已经工作很长时间了,而没有需要这个)。你应该把学习重点放在你实际可以使用的东西上。 - Bo Persson
显示剩余9条评论
1个回答

2
让我先澄清一下负索引混淆的问题,因为我们稍后将用它来回答其他问题:
当我说 *(cpp[-2]) 时,我得到的是 QUIZ,但我更希望得到一些垃圾值。
负值是可以的。请注意以下内容:
按照定义,下标运算符 E1[E2]*((E1)+(E2)) 完全相同。
知道了这一点,既然 cpp == cp+2,那么:
cpp[-2] == *(cpp-2) == *(cp+2-2) == *cp == c+3

因此:
*cpp[-2]+3 == *(c+3)+3 == c[3]+3

这句话的意思是,它表示“QUIZ”的地址再加上一个指向char类型的指针的3个位置,因此你将字符“Z”的地址传递给printf函数,这意味着它将从那里开始打印字符串。
实际上,如果你想知道,"-2[cpp]"也是等效且有效的。
现在,问题是:
1. *--*++cpp+3 这个魔法是如何工作的?我的意思是,-- 部分修改了什么,使得我通过像这样解引用 **cpp 能够获得 MCQ 而不是 TEST。我假设指针 *++cpp+3 在 -- 应用后保留了状态,但我还不能想象它是如何工作的。
让我们来分解一下(请记住,在这里,cpp == cp+1,正如你正确指出的那样):
    ++cpp   // cpp+1 == cp+2 (and saving this new value in cpp)
   *++cpp   // *(cp+2) == cp[2]
 --*++cpp   // cp[2]-1 == c (and saving this new value in cp[2])
*--*++cpp   // *c
*--*++cpp+3 // *c+3

这指向了您正确指出的。然而,cpp和cp [2]已被修改,所以现在您有:
cp[] == {c+3, c+2, c, c}
cpp  == cp+2

事实上,问题的其余部分并没有使用 cp [2] 发生变化,但是需要注意——特别是因为您打印了指针的值。请看:
  1. Why the following works the way it works (the cpp[-2] part):

    printf("%p\n", &*cpp[1]); // 0x601060 -> c
    printf("%p\n", &*(cpp[0])); // 0x601068 -> c+1
    printf("%p\n", &*(cpp[-1])); // 0x601070 -> c+2
    printf("%p", &*(cpp[-2])); // 0x601078 -> c+3
    
首先,让我们将&*x简化为x。然后,做与上面类似的事情,如果cpp == cp+2(如上所述),您可以看到:
cpp[ 1] == cp[3] == c
cpp[ 0] == cp[2] == c   // Note this is different to what you had
cpp[-1] == cp[1] == c+2
cpp[-2] == cp[0] == c+3

“我显然混淆了很多东西,可能会称某些东西为指针,但实际上并不是指针,我想理解指针的概念,所以如果有人能告诉我哪里错了,我会很高兴。”
你的理解其实已经很好了!基本上,指针是代表内存地址的整数。但是,当你对它执行算术运算时,它会考虑到它所指向类型的大小。这就是为什么,如果c == 0x601060且sizeof(char*) == 8,那么:

c+1 == 0x601060 + 1*sizeof(char*) == 0x601068 // Instead of 0x601061
c+2 == 0x601060 + 2*sizeof(char*) == 0x601070 // Instead of 0x601062

1
是的,我确实没有足够注意到 *((E1)+(E2)),但现在一切都变得很清晰了。而且你对于 cpp[ 0] == cp[2] == c // 注意这和你之前写的不同 的说法是正确的,那是因为我将 printf("%s ", *--*++cpp+3); 注释掉了,并在后面只放置了 ++cpp,以测试它如何影响结果,然后忘记恢复了。 - MisterAlejandro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接