C указание и инициализация двухсимвольного указателя - PullRequest
24 голосов
/ 28 января 2011

Я всегда думал, что объявление

char *c = "line";

было таким же, как

char c[] = "line";

, и поэтому я сделал

char **choices = { "New Game", "Continue Game", "Exit" };

, что дает мне несовместимый тип указателягде

char *choices[] = { "New Game", "Continue Game", "Exit" };

нет.Любая помощь в понимании этого?

Ответы [ 4 ]

16 голосов
/ 28 января 2011
char *c = "line";

равно , а не так же, как

char c[] = "line";

, это действительно то же самое, что

static char hidden_C0[] = "line";
char *c = hidden_C0;

, за исключением того, что переменная hidden_C0 не является прямойдоступны.Но вы увидите это, если сбросите сгенерированный ассемблер (обычно он имеет имя, которое не является допустимым идентификатором C, например .LC0).И в вашем примере с массивом строковых констант происходит то же самое:

char *choices[] = { "New Game", "Continue Game", "Exit" };

становится

char hidden_C0[] = "New Game";
char hidden_C1[] = "Continue Game";
char hidden_C2[] = "Exit";

char *choices[] = { hidden_C0, hidden_C1, hidden_C2 };

Теперь это поведение особого случая, которое доступно только для строковых констант.Вы не можете написать

int *numbers = { 1, 2, 3 };

, вы должны написать

int numbers[] = { 1, 2, 3 };

, и поэтому вы не можете написать

char **choices = { "a", "b", "c" };

.

(Ваше заблуждение является частным случаем распространенного заблуждения о том, что массивы "совпадают с" указателями в C. Они не являются. Массивы являются массивами. Переменные с типами массивов страдают затуханием типа от типа указателя, когда они используется (почти во всех контекстах), но не тогда, когда они определены. *

13 голосов
/ 28 января 2011

Ну, они не одинаковы.Большинству людей проще думать о них как об одном и том же, так что все начинают думать таким образом, пока не столкнутся с проблемой, подобной приведенной выше: -)

Я собирался написать что-то длинное и задумчивое, нотогда я понял ... Кто-то другой, должно быть, уже сделал это.И они есть.Это довольно хорошее объяснение:

http://www.lysator.liu.se/c/c-faq/c-2.html

Самый простой способ думать об этом - когда вы делаете что-то вроде:

  char *foo = "something";

Вы действительноделать что-то вроде:

  char randomblob[] = "something";
  char *foo = randomblob;

Теперь ... это не совсем точная картина (хотя я не эксперт по компилятору).По крайней мере, это позволяет вам думать о вещах немного более правильно.

Итак, вернемся к вашей проблеме, если I правильно все понимает (что никогда не гарантируется), вы не сможетесделайте ваш пример строки № 3 в C. Вы правы, что кто-то может написать компилятор, который будет делать все правильно, но gcc этого не делает.Четвертый пример, однако, делает «правильную вещь» и дает вам «массив указателей, каждый из которых сам указывает на массив const char».

Однажды я наткнулся на веб-страницу, которая переводила бы комплексС типом на английском.Хотя, вероятно, это было в начале 90-х, но держу пари, что если вы достаточно погуглите, это даст вам более точное описание формулировок, чем то, которое я только что написал.

3 голосов
/ 28 января 2011

Все нормально, просто напишите

char **choices = (char *[]){ "New Game", "Continue Game", "Exit" };

Однако, choices можно использовать только для линейной адресации.Например:

printf ("%s", &(*choices)[0]); выходы: New Game
printf ("%s", &(*choices)[1]); выходы: ew Game
printf ("%s", &(*choices)[9]); выходы: Continue Game

Так что это не шуткаЭто действительная инициализация.Просто другой вид использования.


Вы также можете найти очень близкий пример здесь , объясняющий понятие Составные литералы .

2 голосов
/ 28 января 2011

Онлайн стандарт C (черновик n1256 ):

6.7.8 Инициализация
...
11 Инициализатором скаляра должно быть одиночное выражение , необязательно заключенное в фигурные скобки. Начальное значение объекта - это значение выражения (после преобразования); применяются те же ограничения и преобразования типов, что и для простого присваивания, принимая тип скаляра как неквалифицированную версию объявленного им типа.
...
16 В противном случае инициализатор для объекта, который имеет агрегатный или объединенный тип , должен представлять собой заключенный в скобки список инициализаторов для элементов или именованных членов.

Акцент добавлен.

char ** - это скалярный тип, а не агрегат, поэтому он не совместим с инициализатором {"New Game", "Continue Game", "Exit"}. Напротив, char *[] является агрегатным (массивом) типом.

Точно так же вы не могли написать что-то вроде

int *foo = {1, 2, 3};

потому что int * не является типом массива.

Ваше понимание

char *c = "line";

и

char c[] = "line";

слегка выключен; они не одинаковы. Первая форма копирует адрес строкового литерала в значение указателя c. Вторая форма копирует содержимое выражения массива "line" в буфер, обозначенный c.

...