объяснение вывода двойного указателя - PullRequest
0 голосов
/ 22 июня 2019

Не могли бы вы объяснить, как вывод -4?Я думаю ++pp; это UB, но не уверен.Ваше объяснение действительно поможет в понимании.Может ли быть какая-либо разница в выходных данных в машине с прямым или обратным порядком байтов?

#include <stdio.h>

int a[] = { -1, -2, -3, -4 };
int b[] = { 0, 1, 2, 3 };

int main(void)
{
    int *p[] = { a, b };
    int **pp = p;
    printf("a=%p, b=%p, p=%p, pp=%p\n", (void*)a, (void*)b, (void*)p, (void*)pp);
    ++pp;
    printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
    ++*pp;
    printf("p=%p, pp=%p *pp=%p\n", (void*)p, (void*)pp, (void*)*pp);
    ++**pp;

    printf("%d\n", (++**pp)[a]);
}

Мой вывод:

a=0x107121040, b=0x107121050, p=0x7ffee8adfad0, pp=0x7ffee8adfad0
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121050
p=0x7ffee8adfad0, pp=0x7ffee8adfad8 *pp=0x107121054
-4

Идеальный вывод

Ответы [ 3 ]

4 голосов
/ 22 июня 2019

Когда вы используете имя массива (в большинстве случаев), он распадается на указатель на свой первый элемент.Это означает, что int* p = a; и int* p = &a[0]; абсолютно одинаковы.

Итак, чтобы понять, что происходит в этом случае, просто пройдите шаг за шагом.В момент вашего первого printf вызова все выглядит так:

 pp            p           a
+-------+     +------+     +----+----+----+----+
|   +--------->   +--------> -1 | -2 | -3 | -4 |
+-------+     |      |     +----+----+----+----+
              |      |
              +------+     b
              |      |     +----+----+----+----+
              |  +---------> 0  | 1  | 2  | 3  |
              |      |     +----+----+----+----+
              +------+

pp указывает на первый элемент p, который является указателем на первый элемент a.

Теперь, когда вы увеличиваете pp, он изменяется, чтобы указывать на второй элемент p, который является указателем на первый элемент b:

 pp            p           a
+-------+     +------+     +----+----+----+----+
|   +   |     |   +--------> -1 | -2 | -3 | -4 |
+---|---+     |      |     +----+----+----+----+
    |         |      |
    |         +------+     b
    |         |      |     +----+----+----+----+
    +--------->  +---------> 0  | 1  | 2  | 3  |
              |      |     +----+----+----+----+
              +------+

Затем вы увеличиваете *pp.Поскольку *pp является указателем на первый элемент b, этот указатель увеличивается для указания на второй элемент b:

 pp            p           a
+-------+     +------+     +----+----+----+----+
|   +   |     |   +--------> -1 | -2 | -3 | -4 |
+---|---+     |      |     +----+----+----+----+
    |         |      |
    |         +------+     b
    |         |      |     +----+----+----+----+
    +--------->      |     | 0  | 1  | 2  | 3  |
              |   +  |     +----+-^--+----+----+
              +---|--+            |
                  +---------------+

Затем вы увеличиваете **pp.В этот момент pp является указателем на второй элемент p, поэтому *pp является указателем на второй элемент b.Это означает, что **pp называет второй элемент b.Вы увеличиваете это значение с 1 до 2:

 pp            p           a
+-------+     +------+     +----+----+----+----+
|   +   |     |   +--------> -1 | -2 | -3 | -4 |
+---|---+     |      |     +----+----+----+----+
    |         |      |
    |         +------+     b
    |         |      |     +----+----+----+----+
    +--------->      |     | 0  | 2  | 2  | 3  |
              |   +  |     +----+-^--+----+----+
              +---|--+            |
                  +---------------+

Теперь давайте рассмотрим (++**pp)[a].++**pp такой же, как и раньше, поэтому второй элемент b увеличивается до 3.

Теперь для любого указателя ptr и целого числа n, ptr[n] одинаковыкак *(ptr + n).Поскольку сложение коммутативно, ptr + n совпадает с n + ptr.Это означает, что ptr[n] - это то же самое, что и n[ptr].

. Сочетание этих значений означает, что (++**pp)[a] - это то же самое, что и 3[a], что совпадает с a[3].a[3] равно -4, следовательно, ваш результат.

1 голос
/ 22 июня 2019

Это проще всего понять, если вы выражаете все имена массивов в выражениях в виде их затухающих значений. arrayName как указатель становится &arrayName[0]. Итак, после всех инициализаций у вас есть:

a[0] = -1, a[1] = -2, a[2] = -3, a[3] = -4
b[0] = 0, b[1] = 1, b[2] = 2, b[3] = 3
p[0] = &a[0], p[1] = &b[0]
pp = &p[0]

При увеличении указателя он указывает на следующий элемент массива, поэтому после ++pp теперь у нас есть

pp = &p[1]

++*pp разыменования pp, так что это эквивалентно ++p[1], так что теперь у нас есть

p[1] = &b[1]

++**pp разыменовывает это дважды, так что это эквивалентно ++b[1], так что теперь у нас есть

b[1] = 2

Наконец, у нас действительно запутанное выражение (++**pp)[a]. ++**pp снова увеличивает b[1], поэтому его значение теперь равно 3, и это значение заменяет это выражение, поэтому оно эквивалентно 3[a]. Это может выглядеть как бессмыслица (3 не является массивом, как вы можете его индексировать?), Но оказывается, что в C x[y] == y[x] из-за способа индексации определяется в терминах арифметики указателей. Таким образом, 3[a] совпадает с a[3], а последняя строка печатает -4.

1 голос
/ 22 июня 2019

Запомните определение оператора подписки [], например, как определено в этом онлайн-проекте стандарта C:

6.5.2.1 Массив подписки

2) ... Определение оператора индекса [] состоит в том, что E1 [E2] идентично (* ((E1) + (E2))). ...

В нем говорится, что E1 [E2] идентичен (* ((E1) + (E2)). Затем становится ясно, что (++**pp)[a] совпадает с *((++**pp)+(a)), что опять-таки совпадает с *((a)+(++**pp)) который, следовательно, читается как a[(++**pp)]. Значение ++**pp равно 3, а a[3] равно -4.

...