【问题标题】:Understanding C pointers, arrays and negative indices理解 C 指针、数组和负索引
【发布时间】:2018-05-25 18:31:13
【问题描述】:

我正在尝试学习 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 这样取消引用时,我预计会得到 MCQ 的值 c+1

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

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

所以我的问题是:

  1. *--*++cpp+3 的魔法是如何工作的,我的意思是 -- 部分修改的内容,当我像这样取消引用 **cpp 时,允许我得到 MCQ 而不是 TEST,我假设这个指针*++cpp+3 保留了应用-- 后的状态,但还无法想象它是如何工作的。

  2. 为什么下面的工作方式是这样的(cpp[-2] 部分):

    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
    

它似乎有相反的顺序,我可以接受&amp;*(cpp[0]) 指向c+1,但我希望&amp;*cpp[1] 指向c+2,而&amp;*(cpp[-1]) 指向c。我在这个问题中找到了:Are negative array indexes allowed in C?

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

【问题讨论】:

  • 如果有人认为只有了解这种糟糕的编码实践才能学习一门语言,那他们就错了。放弃他们。
  • 您可能缺少的是方括号可以被认为是一个运算符,因此cpp[n] 一词实际上与*(cpp + n) 相同。因此,n 的负值是可以接受的,但在实践中很少使用。所以,负下标只是从指针 cpp 向后移动。
  • @RSahu 说真的。当老师选择提出这样的“陷阱”问题时,我无法忍受。这是一种糟糕的编码实践,任何人都不应该处理。如果这成为生产代码,则意味着该公司未能实施最低限度的代码审查实践。
  • 我意识到这个*--*++cpp+3 不应该这样写,但是测验中那些棘手的问题仍然帮助我提高了对指针的理解,别担心,你不会的看不到我的 C 代码,我只会学习指针并离开)
  • @MisterAlejandro - 我们只是想说倾斜*--*++cpp+3 并不重要,因为它从未被使用过。我不知道是做什么的,但我不在乎(检查我的grey beard 看看我在不需要这个的情况下工作了多长时间)。你应该把学习的重点放在你可以实际使用的东西上。

标签: c arrays pointers


【解决方案1】:

让我先澄清一下否定索引的混淆,因为我们稍后会用它来回答其他问题:

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

负值很好。 Note the following:

根据定义,下标运算符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个位置,所以你将"QUIZ"中的字符Z的地址传递给printf,这意味着它将开始打印字符串从那里开始。

实际上,如果您想知道,-2[cpp] 也是等效且有效的。


现在,问题:

  1. *--*++cpp+3 的魔力是如何工作的,我的意思是,当我像 **cpp 这样取消引用时,允许我得到 MCQ 而不是 TEST 的 -- 部分修改了什么,我假设这个指针 @ 987654339@ 保留了应用 -- 后的状态,但还无法想象它是如何工作的。

让我们分解一下(回想一下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

正如您正确指出的那样,这指向sQuiz。但是,cppcp[2] 已被修改,因此您现在拥有:

cp[] == {c+3, c+2, c, c}
cpp  == cp+2

cp[2] 已更改的事实并未在问题的其余部分中使用,但重要的是要注意 - 特别是因为您打印了指针的值。见:

  1. 为什么下面的工作方式是这样的(cpp[-2] 部分):

    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
    

首先,让我们将&amp;*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
  1. 我显然混淆了很多东西,并且可能将某些东西称为实际上不是指针的东西,我想掌握指针的概念,所以如果有人告诉我我哪里错了,我会很高兴。

其实你做得很好! :-)

基本上,指针是表示内存地址的整数。但是,当您对其执行算术运算时,它会考虑它指向的类型的大小。这就是为什么,如果c == 0x601060sizeof(char*) == 8,那么:

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

【讨论】:

  • 是的,我没有对这个*((E1)+(E2)) 给予足够的重视,但现在一切对我来说都是那么清楚。你对cpp[ 0] == cp[2] == c // Note this is different to what you had 是正确的,发生这种情况是因为我注释掉了printf("%s ", *--*++cpp+3); 并在后面的行中放置了++cpp,以便测试它如何影响结果,并忘记将其恢复)
猜你喜欢
  • 2015-02-12
  • 1970-01-01
  • 2018-04-27
  • 1970-01-01
  • 2011-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多