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


463

В чем разница между следующими декларациями:

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

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


54
Вот отличная статья о чтении сложных объявлений в C: unixwiz.net/techtips/reading-cdecl.html
jesper

@jesper К сожалению, constи volatileклассификаторы, которые являются важным и сложным, не хватает в этой статье.
не пользователь

@ not-a-user, которые не имеют отношения к этому вопросу. Ваш комментарий не актуален. Пожалуйста, воздержитесь.
user64742

Ответы:


439
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 * обр [8]: 8x4 байт выделено всего 4 байта для каждого указателя. int (* arr) [8] является правильным, 4 байта.
Мехрдад Афшари

2
Я должен был перечитать то, что я написал. Я имел в виду 4 для каждого указателя. Спасибо за помощь!
Джордж

4
Причина, по которой первый совпадает с последним, заключается в том, что всегда разрешается заключать скобки в деклараторы. P [N] является декларатором массива. P (....) является декларатором функции, а * P является декларатором указателя. Таким образом, все в следующем не отличается от круглых скобок (за исключением одной из функций '"()": int (((* p))); void ((g (void))); int * (a [1]); void (* (p ())).
Йоханнес Шауб -

2
Хорошо сделано в вашем объяснении. Более подробное описание приоритетов и ассоциативности операторов см. На странице 53 языка программирования C (второе издание ANSI C) Брайана Кернигана и Денниса Ричи. Операторы ( ) [ ] связываются слева направо и имеют более высокий приоритет, чем *считанные int* arr[8]как массив размера 8, где каждый элемент указывает на int, и int (*arr)[8]как указатель на массив размера 8, который содержит целые числа
Mushy

267

Используйте программу 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 )

@ankii Большинство дистрибутивов Linux должны иметь пакет. Вы также можете создать свой собственный двоичный файл.
sigjuice

ах извините, что не упомянул, macOS здесь. увидим, если доступно, в противном случае сайт тоже хорошо. ^^ спасибо, что сообщили мне об этом .. Не стесняйтесь помечать NLN.
анки

2
@ankii Вы можете установить с Homebrew (и, может быть, MacPorts?). Если они вам не по вкусу, создать свою собственную ссылку Github в правом верхнем углу cdecl.org довольно просто (я только что создал ее на macOS Mojave). Затем просто скопируйте двоичный файл cdecl в вашу переменную PATH. Я рекомендую $ PATH / bin, потому что нет необходимости включать root в такие простые вещи, как эта.
sigjuice

О, не читал маленький параграф об установке в readme. только некоторые команды и флаги для обработки зависимостей. Устанавливается с помощью brew. :)
анки

1
Когда я впервые прочитал это, я подумал: «Я никогда не спущусь до этого уровня». На следующий день я скачал его.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功

126

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

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

int* arr1[8];

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

int (*arr2)[8];

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

int *(arr3[8]);

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

Это должно помочь вам со сложными декларациями.


19
Я слышал, что это называется «Правило спирали», которое можно найти здесь .
четверка,

6
@InkBlend: спиральное правило отличается от правого-левого . Первое терпит неудачу в случаях, как, в int *a[][10]то время как второе преуспевает.
legends2k

1
@dogeen Я думал, что этот термин как-то связан с
Бьярном

1
Как сказали InkBlend и legends2k, это спиральное правило, которое является более сложным и работает не во всех случаях, поэтому нет причин использовать его.
Котломой

Не забудьте ассоциативность ( ) [ ]слева направо и справа налево* &
Mushy

28
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 

Не должно ли быть третье: a - это массив указателей на целочисленный массив размером 8? Я имею в виду, что каждый из целочисленных массивов будет иметь размер 8, верно?
Рушил Пол

2
@Rushil: нет, последний индекс ( [5]) представляет внутреннее измерение. Это означает, что (*a[8])это первое измерение и, следовательно, внешнее представление массива. То, на что a указывает каждый элемент в пределах, - это другой целочисленный массив размером 5.
zeboidlund

Спасибо за третий. Я ищу, как написать массив указателей на массив.
Deqing

15

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

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

int (*arr2)[8];

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

int *(arr3[8]);

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

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

float *(*x)(void )

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

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

редактировать: пример

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

#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 &lt; 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]

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

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



3

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


Это правда, но это также незаконно. Вы не можете иметь функцию, которая возвращает массив. Я попробовал и получил это: 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
Cacahuete Frito

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

Хорошо, если я это понимаю, вы говорите о создании функции, которая возвращает указатель на первый элемент массива (не сам массив), а позже используете эту функцию, как если бы она возвращала массив? Интересная идея. Я попытаюсь. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }и назовите это так: foo(arr)[4];что должно содержать arr[2][4], верно?
Какауэте Фрито

верно ... но вы тоже были правы, и заявление было незаконным. :)
Луис Колорадо

2

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

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

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


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


2

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

int *something[n];

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

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

int *(something[i]);

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

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

Во втором

int (*something)[n];

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

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

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


0

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

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

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

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


0
int *arr1[5]

В этом объявлении arr1представлен массив из 5 указателей на целые числа. Причина: квадратные скобки имеют более высокий приоритет над * (оператор разыменования). И в этом типе число строк фиксировано (5 здесь), но количество столбцов является переменным.

int (*arr2)[5]

В этом объявлении arr2указатель на целочисленный массив из 5 элементов. Причина: здесь скобки () имеют более высокий приоритет, чем []. И в этом типе число строк является переменным, но количество столбцов является фиксированным (5 здесь).


-7

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

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


« в массиве указателей, если указатель увеличивается, он переходит к следующему массиву », это совершенно неправильно.
алк
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.