C указатель на массив / массив указателей неоднозначности

432

В чем разница между следующими объявлениями:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Каково общее правило для понимания более сложных объявлений?

  • 48
    Вот отличная статья о чтении сложных объявлений в C: unixwiz.net/techtips/reading-cdecl.html
  • 0
    @jesper К сожалению, в этой статье отсутствуют const и volatile квалификаторы, которые являются важными и хитрыми.
Теги:
arrays
pointers
variable-declaration

11 ответов

374
Лучший ответ
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

Третий - тот же, что и первый.

Общее правило приоритет оператора. Это может стать еще более сложным, поскольку указатели на функцию попадают в изображение.

  • 4
    Итак, для 32-битных систем: int * arr [8]; / * Выделено 8x4 байта для каждого указателя / int (* arr) [8]; / Выделено 4 байта, только указатель * /
  • 10
    Нету. int * arr [8]: всего выделено 8x4 байта, 4 байта для каждого указателя. int (* arr) [8] является правильным, 4 байта.
Показать ещё 6 комментариев
246

Используйте программу cdecl, как предложено K & R.

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Это работает и наоборот.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )
117

Я не знаю, имеет ли оно официальное имя, но я называю это Right-Left Thingy (TM).

Начните с переменной, затем идите вправо, влево и вправо... и т.д.

int* arr1[8];

arr1 - массив из 8 указателей на целые числа.

int (*arr2)[8];

arr2 - это указатель (блок скобок справа налево) в массив из 8 целых чисел.

int *(arr3[8]);

arr3 - массив из 8 указателей на целые числа.

Это должно помочь вам со сложными объявлениями.

  • 17
    Я слышал, что это называется «Правило спирали», которое можно найти здесь .
  • 5
    @InkBlend: спиральное правило отличается от правого-левого . Первое не работает в случаях, подобных int *a[][10] а второе - успешно.
Показать ещё 6 комментариев
25
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 
  • 0
    Не должно ли быть третье: a - это массив указателей на целочисленный массив размером 8? Я имею в виду, что каждый из целочисленных массивов будет иметь размер 8, верно?
  • 2
    @Rushil: нет, последний индекс ( [5] ) представляет внутреннее измерение. Это означает, что (*a[8]) является первым измерением и, таким образом, является внешним представлением массива. На что указывает каждый элемент в пределах a целочисленного массива размером 5.
Показать ещё 1 комментарий
14

Ответ за последние два также можно вычесть из золотого правила в C:

Декларация следует за использованием.

int (*arr2)[8];

Что произойдет, если вы разыграете arr2? Вы получаете массив из 8 целых чисел.

int *(arr3[8]);

Что произойдет, если вы возьмете элемент из arr3? Вы получаете указатель на целое число.

Это также помогает при работе с указателями на функции. Чтобы взять пример sigjuice:

float *(*x)(void )

Что происходит, когда вы разыскиваете x? Вы получаете функцию, которую вы можете вызвать без аргументов. Что происходит, когда вы это называете? Он вернет указатель на float.

Однако приоритет оператора всегда сложный. Однако использование круглых скобок также может быть путаным, поскольку декларация следует за использованием. По крайней мере, для меня интуитивно arr2 выглядит как массив из 8 указателей на ints, но на самом деле это наоборот. Просто привыкает. Причина достаточно, чтобы всегда добавлять комментарий к этим объявлениям, если вы спросите меня:)

изменить: пример

Кстати, я просто наткнулся на следующую ситуацию: функцию, которая имеет статическую матрицу и которая использует арифметику указателя, чтобы увидеть, находится ли указатель строки вне границ. Пример:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i < 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Вывод:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Обратите внимание, что значение границы никогда не изменяется, поэтому компилятор может оптимизировать это. Это отличается от того, что вы изначально хотели бы использовать: const int (*border)[3]: объявляет границу как указатель на массив из 3 целых чисел, которые не будут изменять значение до тех пор, пока существует переменная. Однако этот указатель может указывать на любой другой такой массив в любое время. Вместо этого мы хотим такого поведения для аргумента (потому что эта функция не меняет ни одного из этих целых чисел). Декларация следует за использованием.

(стр.с.: не стесняйтесь улучшать этот образец!)

5
typedef int (*PointerToIntArray)[];
typedef int *ArrayOfIntPointers[];
2

Вот как я его интерпретирую:

int *something[n];

примечание о приоритете: оператор индекса массива ('[]') имеет более высокий приоритет, чем оператор разыменования ('*').

Итак, здесь мы будем применять '[]' до '*', делая утверждение эквивалентным:

int *(something[i]);

Обратите внимание на то, как декларация имеет смысл: int num означает (num) is (int), int *ptr или int (*ptr) означает, (значение в ptr) является an (int), что делает ptr указателем на int.

Это можно прочитать как (значение (значение в i-м индексе чего-то)) является целым числом. Итак, (значение в i-м индексе чего-то) является (целым указателем), что делает что-то целым целым указателем.

Во втором,

int (*something)[n];

Чтобы понять это утверждение, вы должны быть знакомы с этим фактом:

примечание о представлении указателя массива: somethingElse [i] эквивалентно * (somethingElse + i)

Итак, заменив somethingElse на (* something), мы получим * (* something + i), который является целым числом в соответствии с объявлением. Итак, (* something) дал нам массив, который делает что-то эквивалентное (указатель на массив).

2

Как правило, правильные унарные операторы (например, [], () и т.д.) предпочитают более левые. Таким образом, int *(*ptr)()[]; будет указателем, который указывает на функцию, которая возвращает массив указателей на int (как можно быстрее получить правильные операторы, когда вы выйдете из скобки)

  • 0
    Это правда, но это также незаконно. Вы не можете иметь функцию, которая возвращает массив. Я попытался и получил это: error: 'foo' declared as function returning an array int foo(int arr_2[5][5])[5]; под GCC 8 с $ gcc -std=c11 -pedantic-errors test.c
  • 1
    Причина, по которой компилятор сообщает об этой ошибке, заключается в том, что он интерпретирует функцию как возвращающую массив, как это правильно интерпретирует правило приоритета. Это незаконно как декларация, но юридическое объявление int *(*ptr)(); позволяет использовать выражение типа p()[3] (или (*p)()[3] ) позже.
Показать ещё 2 комментария
2

Я думаю, мы можем использовать простое правило.

example int * (*ptr)()[];
start from ptr 

"ptr является указателем на" пойдите направо..its ")" теперь идите налево его a "(" выходите направо "()" так "к функции, которая не принимает аргументов" go left "и возвращает указатель" go right "to массив "go left" из целых чисел "

  • 0
    Я бы немного улучшил это: «ptr - это имя, которое означает« иди вправо ... это ) , теперь иди налево ... это * »указатель на« иди вправо ... это ) , теперь иди налево ». .. это ( выходите, идите направо () так) к функции, которая не принимает аргументов «идите направо ... [] » и возвращает массив указателей «идите направо ; конец, так что идите налево ... * » "идти налево ... int " целые числа "
0

Я предполагаю, что вторая декларация вводит в заблуждение многих. Вот простой способ понять это.

Позволяет иметь массив целых чисел, т.е. int B[8].

Пусть также имеет переменную A, которая указывает на B. Теперь значение в равно B, то есть (*A) == B. Следовательно, A указывает на массив целых чисел. В вашем вопросе arr похож на A.

Аналогично, в int* (*C) [8], C является указателем на массив указателей на integer.

-8

В указателе на целое число, если указатель увеличивается, он переходит в следующее целое число.

в массиве указателя, если указатель увеличивается, он переходит к следующему массиву

  • 0
    « в массиве указателей, если указатель увеличивается, он переходит к следующему массиву », это совершенно неправильно.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню