Что означает «char (* p) [5];»? - PullRequest
9 голосов
/ 24 июня 2011

человек.

Я пытаюсь понять разницу между этими тремя декларациями:

char p[5];
char *p[5];
char (*p)[5];

Я пытаюсь выяснить это, выполняя некоторые тесты, потому что каждое руководство по чтению объявлений и тому подобного не помогло мне до сих пор. Я написал эту небольшую программу, и она не работает (я пробовал другие виды использования третьего объявления и у меня закончились варианты):

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

int main(void) {                                                                
        char p1[5];                                                             
        char *p2[5];                                                            
        char (*p3)[5];                                                          

        strcpy(p1, "dead");                                                     

        p2[0] = (char *) malloc(5 * sizeof(char));                              
        strcpy(p2[0], "beef");                                                  

        p3[0] = (char *) malloc(5 * sizeof(char));                              
        strcpy(p3[0], "char");                                                  

        printf("p1 = %s\np2[0] = %s\np3[0] = %s\n", p1, p2[0], p3[0]);          

        return 0;                                                               
}

Первый и второй работают хорошо, и я понял, что они делают. Что означает третье объявление и как правильно его использовать?

Спасибо!

Ответы [ 4 ]

13 голосов
/ 24 июня 2011

Третьим является указатель на массив из 5 символов, тогда как вторым является массив из 5 указателей на символ.

Представьте себе так:

________          _____________________
|0x7777| -------> | H | e | l | l | o |
|______|          |_0_|_1_|_2_|_3_|_4_|
    p             ^
                  |
                  0x7777  

Тогда как второй выглядит так:

   "abc"  "def"  "ghi"  "jkl"  "mno"
    ^      ^      ^      ^      ^
    |      |      |      |      |
____________________________________
|0x9999|0x7777|0x3333|0x2222|0x6666|
|___0__|___1__|___2__|___3__|___4__|
                  p

Это один из случаев, когда понимание разницы между указателями и массивами имеет решающее значение.Массив - это объект, размер которого равен размеру каждого из его элементов, умноженному на количество, тогда как указатель - это просто адрес.

Во втором случае sizeof(p) даст 5 * the_size_of_a_pointer.

В третьем случае sizeof(p) даст the_size_of_a_pointer, что обычно составляет 4 или 8, в зависимости от целевой машины.

4 голосов
/ 24 июня 2011
char p[5];      // p is a 5-element array of char
char *p[5];     // p is a 5-element array of pointer to char
char (*p)[5];   // p is a pointer to a 5-element array of char
Синтаксис объявления

C построен на типах выражений , а не на объектах. Идея состоит в том, что форма объявления совпадает с формой выражения, как это будет выглядеть в коде.

В приведенных выше случаях мы имеем дело с массивами. В первом случае p - это массив char; чтобы получить доступ к конкретному символьному значению, мы просто указали бы в массиве:

val = p[i];

Тип выражения p[i] равен char, поэтому объявление p равно char p[5].

В следующем случае p - это массив указателей на char; чтобы получить доступ к значению, мы индексируем массив и разыменовываем результат:

val = *p[i];

Операторы постфикса, такие как [], имеют более высокий приоритет, чем унарные операторы, такие как *, поэтому вышеприведенный анализируется как

val = *(p[i]);

Тип выражения *p[i] равен char, поэтому объявление p равно char *p[5].

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

val = (*p)[i];

Поскольку [] имеет более высокий приоритет, чем унарный *, мы должны использовать скобки, чтобы явно сгруппировать * с p (в отличие от p[i]). Опять же, тип выражения (*p)[i] равен char, поэтому объявление p равно char (*p)[5].

EDIT

Указатели на массивы отображаются в следующих контекстах:

  1. Вы явно берете адрес N-мерного массива:

    int x[10];
    int (*px)[10] = &x;
    
    Обратите внимание, что хотя выражения x и &x дают одно и то же значение (адрес первый элемент массива), они имеют различные типы (int * против int (*)[10]).
  2. Вы динамически распределяете объект типа массива:

    int (*px)[10] = malloc(sizeof *px);
    
  3. N-мерное выражение массива «разлагается» на указатель на (N-1) -размерный массив:

    int x[10][20];
    foo(x);
    ...
    void foo(int (*px)[20]){...}
    
4 голосов
/ 24 июня 2011

Это указатель на массив символов. Это объясняется в C FAQ . В будущем, когда вы не понимаете декларацию, используйте cdecl или cdecl(1).

3 голосов
/ 24 июня 2011
char (*p3)[5];

фактически объявляет указатель на массив 5 char. Это означает, что он должен указывать на указатель, который указывает на массив 5 char. Он не выделяет 5 из чего-либо, только указатель, и он должен указывать на то, что указывает на 5 char.

Итак, правильное использование:

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

int main(void) {                                                                
        char p1[5];                                                             
        char *p2[5];                                                            
        char (*p3)[5];                                                          

        strcpy(p1, "dead");                                                     

        p2[0] = (char *) malloc(5 * sizeof(char));                              
        strcpy(p2[0], "beef");                                                  

        p3 = &p1;                                                               

        printf("p1 = %s\np2[0] = %s\np3 = %s\n", p1, p2[0], *p3);               

        return 0;                                                               
}

Если вы хотите получить доступ к одной позиции этой строки p1, используя p3 для доступа к ней, вы должны, например, сделать (*p3)[2]. Это получит доступ к третьей позиции. Это потому, что сначала вы должны преобразовать p3 в p1 (разыменование (*p3) перед выполнением арифметики позиции), а затем получить доступ к третьей позиции этого адреса (указанной указателем с разыменованным указателем).

...