В чем разница между указателем на массив 2D char и указателем на массив 2D int?

    #include <stdio.h>

    int main()
    {
        char str[3][15] = {"Pointer to","char","program"};
        char (*pt)[15] = str; // statement A
        char *p = (char *)str; // statement B
        printf("%s\n",p[3]); // statement C  - Seg Fault in this line
        printf("%s\n",p); // working properly displaying "Pointer to"
        printf("%s\n",p+1); // here it is pointing to second element of first array so displaying "ointer to"
        printf("%s\n",pt+1); // printing properly "char" as expected
        int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
        int (*nm)[3] = num[1];
        int *n = num;
        printf("n - %d\n",n[10]); // statement D
        printf("nm - %d\n",nm[0][0]);
        return 0;
    }

Мои вопросы:

  1. Пожалуйста, помогите мне получить четкое представление о механизме хранения данных в случае массива char и массива int

  2. В приведенной выше программе я понимаю, что когда указатель на массив символов указывает на 2D-массив символов, как показано в операторе A, он отображается правильно, но когда на него указывает обычный указатель char и пытается напечатать char в операторе C, он получает SegFault , вместо этого он должен печатать 'n' (3-й номер char в первом массиве «Указатель на»), поэтому возникает путаница, почему в случае массива int я получаю правильный элемент n = 11 в операторе D и почему в этом случае (оператор C) это не правильно печатает.

  3. Как данные будут храниться в случае массива символов, будут ли они храниться в этой форме, показанной ниже.


char str[3][15] = {{'P','o','i','n','t','e','r',' ','t','o'},
                   {'c','h','a','r'},
                   {'p','r','o','g','r','a','m'}};

если он хранится так, то он должен работать как массив целочисленных указателей, показанный в операторе D. Пожалуйста, помогите мне разобраться с этой проблемой и прояснить проблему, которая у меня есть в случае хранения массива char и int.


person ssg    schedule 28.07.2016    source источник
comment
оператор D работает, потому что num выделяется как единый массив, к которому компилятор обращается по следующей формуле: если оператор num[row][col], то он такой же, как *((int *)num + row * 4 + col).   -  person miravalls    schedule 28.07.2016
comment
оператор B такой же, как p = str[0], потому что массив объявляется в стеке. p[3] возвращает символ, а не адрес, как отмечает @LPs. Если вы хотите напечатать p[3], спецификатор формата должен быть %c.   -  person miravalls    schedule 28.07.2016


Ответы (2)


Давайте посмотрим на ваш код шаг за шагом.

char str[3][15] = {"Pointer to","char","program"};

Здесь вы создали массив из трех массивов из пятнадцати char. И вы инициализируете каждый из массивов char строковыми литералами. Если литералы короче массива - последние элементы заполняются нулями, то есть то же самое, что:

char str[3][15] = {
    {'P', 'o', 'i', 'n', 't', 'e', 'r', ' ', 't', 'o', 0, 0, 0, 0, 0},
    {'c', 'h', 'a', 'r', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    {'p', 'r', 'o', 'g', 'r', 'a', 'm', 0, 0, 0, 0, 0, 0, 0, 0}
};

потом

char (*pt)[15] = str; // statement A

Здесь вы создаете pt как указатель на массив из пятнадцати char, и вы инициализируете его адресом str[0], т.е. pt указывает на первый массив char[15] из str.

Следующий

char *p = (char *)str; // statement B

Это не нормально. Насколько я вижу - вы пытаетесь заставить p указывать на первый char в памяти, которую занимает str. Выражение str имеет тип char (*)[15], т.е. это указатель на массив символов, а не указатель на char (и из-за этого вы вынуждены использовать приведение типов), И независимо от того, что str действительно указывает на ячейку, где хранится 'P' - вы должны сделать это более безопасным способом:

char *p = &str[0][0]; // the same as "p = str[0];"

str[0] ссылается на первый элемент str, т.е. тип str[0] представляет собой массив из пятнадцати char, тогда вы можете просто сослаться на первый char и взять его адрес - &(str[0])[0], или просто использовать тот факт, что выражение с типом "массив" распадается на введите "указатель на первый элемент массива", поэтому str[0] тоже работает.

Вперед

printf("%s\n",p[3]); // statement C  - Seg Fault in this line

Эта строка вызывает неопределенное поведение, поскольку спецификатор формата требует, чтобы второй аргумент был const char *, а вы передаете char. Если вы хотите напечатать один символ - сделайте это:

printf("%c\n", p[3]); // prints "n"

потом

    printf("%s\n",p); // working properly displaying "Pointer to"
    printf("%s\n",p+1); // here it is pointing to second element of first array so displaying "ointer to"

Они хорошо работают, потому что тип второго аргумента правильный, и мы знаем, что строки заканчиваются нулем.

printf("%s\n",pt+1); // printing properly "char" as expected

Откровенно говоря - это неверно, так как pt + 1 это "указатель на массив char", а надо передать "указатель на char". Его следует переписать так:

printf("%s\n",*(pt+1)); // or pt[1]

Но, похоже, это работает, потому что независимо от несовместимости типов оба указателя указывают на одно и то же место.

Следующий раздел о int.

int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*nm)[3] = num[1];
int *n = num;

Вот две ошибки: nm не следует инициализировать с помощью num[1], так как они имеют несовместимые типы: "указатель на массив из трех int" против "массив из четырех int/указатель на int" (благодаря распаду). И n нельзя инициализировать с помощью num, потому что у них тоже несовместимые типы. Согласно моему предположению о том, что вы хотите, это должно выглядеть следующим образом:

int num[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*nm)[4] = num + 1;
int *n = &num[0][0];

И последние:

printf("n - %d\n",n[10]); // statement D
printf("nm - %d\n",nm[0][0]);

Разыменование корректно и передает аргументы тоже, но имейте в виду, что указатели были инициализированы неправильно.

Надеюсь, я ответил на все ваши вопросы.

person Sergio    schedule 28.07.2016
comment
Здорово. лучше, чем у меня. ;) - person LPs; 28.07.2016

Ваш segfault связан с тем, что вы передаете неправильный тип в printf.

Написав p[3], вы ссылаетесь на указатель на 4-й char первой строки матрицы str. То же, что *(p+3)

Если вы хотите напечатать 3-й символ, вы должны

printf("%c\n",p[3]);

Если вы хотите напечатать первую C-строку (строка 0 матрицы), вы должны:

printf("%s\n",&p[3]);

потому что %s хочет char *.

Если вы добавите, по крайней мере для gcc, опцию -Wall в свою команду, компилятор покажет вам хорошее и полезное предупреждение:

test.c:8:9: warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘int’ [-Wformat=]
         printf("%s\n",p[3]); // statement C  - Seg Fault in this line

Что касается вопроса 3, вы должны принять к сведению, что правильное хранение:

char str[3][15] = {{'P','o','i','n','t','e','r',' ','t','o','\0'},
                   {'c','h','a','r','\0'},
                   {'p','r','o','g','r','a','m','\0'}};

из-за того, что C-String завершаются нулем, поэтому, например, строка "Pointer to" будет занимать 11 символов


Последнее, что касается указателя int. Это хорошо работает, потому что спецификатор формата %d требует значение int, а не адрес. Итак, пишем:

printf("n - %d\n",n[10]);

полностью правильно, потому что n[10] разыменовывает 11-й элемент матрицы num, что означает 3-й элемент 3-й строки. То же, что и *(n+10)

person LPs    schedule 28.07.2016
comment
Написав p[3], вы привязываете указатель к третьему символу Это неверно. - person 2501; 28.07.2016
comment
Использование p для указания на границы внутреннего массива, например p[10], является неопределенным поведением. - person 2501; 28.07.2016
comment
@ 2501 Хорошо для первого комментария, я отредактировал. Но я не понимаю вас насчет второго. - person LPs; 28.07.2016
comment
Извините, я имел в виду указатель n. Его инициализация неверна, но если мы проигнорируем это, n указывает на num[0], который имеет тип int[4]. Доступ n[10] находится за пределами допустимого. - person 2501; 28.07.2016
comment
@ 2501 Я догадался. Наверное, загорание на берегу моря в последние дни взорвало мне мозг, но я действительно не понимаю тебя. num является матрицей 3x4. Память непрерывна, поэтому, начиная с &num[0][0], есть 12 int. Итак, n[10] входящий. Мне нужен кофе? ;) - person LPs; 28.07.2016
comment
Читайте 6.5 8 в последнем стандарте. Доступ к объекту за пределами границ является неопределенным поведением. Если есть другой объект, смежный с этим, не имеет значения. - person 2501; 28.07.2016
comment
@2501 Ну понял. Но есть ли у вас пример, в котором строки матрицы не являются смежными и поведение действительно не определено? - person LPs; 28.07.2016
comment
Поведение не определено из-за 6.5 8. Является ли массив непрерывным или нет, не имеет значения. - person 2501; 28.07.2016