Как работает массив указателей на указатели? - PullRequest
6 голосов
/ 19 июня 2009
char **Data[70]={NULL};  

Какова правильная терминология для этого? Как еще это можно написать? Как это выглядит в памяти? Я читаю много уроков по указателям, но не вижу этого в этом синтаксисе. Любая помощь приветствуется. Благодаря.

Ответы [ 5 ]

24 голосов
/ 19 июня 2009

Эта структура

char **Data[70]={NULL};

- это массив из 70 указателей на указатели на символ. Компилятор выделяет 70 * sizeof(char**) байтов для этого массива, что предполагает, что 32-битные указатели равны 280 байтов.

Если вы внутренне думаете о «указателе на символ» как о строке, которая не соответствует действительности, но достаточно близка, то это массив из 70 указателей на строки. Чтобы создать ASCII-арт и сделать вид, что вы выделили и заполнили некоторые значения ....

 Array of        One or more
 char **           char *
+---------+     +---------+
|    0    | --> |   ptr   | -->  "Hello, world"
+---------+     +---------+
|    1    |
+---------+       +---------+
|    2    | ----> |  ptr2   | -->  "Goodbye, cruel world"
+---------+       +---------+
|    3    |
+---------+         +---------+
|    4    | ------> | ptr3[0] | -->  "Message 0"
+---------+         +---------+
    ...             | ptr3[1] | -->  "Message 1"
+---------+         +---------+
|   69    |         | ptr3[2] | -->  "Message 2"
+---------+         +---------+

Вы могли бы сделать вышеупомянутое с кодом, подобным этому (ошибка проверки возвращаемых значений malloc пропущена):

char **Data[70]={NULL};
char **ptr, **ptr2, **ptr3;

ptr = (char **) malloc(sizeof(char *));
*ptr = "Hello, world";
Data[0] = ptr;

ptr2 = (char **) malloc(sizeof(char *));
*ptr2 = "Goodbye, cruel world";
Data[2] = ptr2;

ptr3 = (char **) malloc(10 * sizeof(char *));
Data[4] = ptr3;

ptr3[0] = "Message 0";
ptr3[1] = "Message 1";
 ...
ptr3[9] = "Message 9"; 

printf("%s\n", *Data[0]);
printf("%s\n", Data[2][0]);
printf("%s\n", Data[4][0]);
printf("%s\n", Data[4][1]);
      ...
printf("%s\n", Data[4][9]);

Подумайте об этом так: каждая запись в массиве - это char **. Каждая запись может указывать на произвольное местоположение в памяти, причем указанное местоположение (я) равно char * и, следовательно, может указывать на массив символов с нулевым символом в конце, называемый «строка».

Обратите внимание на разницу между этим и тем, что вы получаете, когда выделяете двумерный массив:

char *Data2[10][70]={NULL};

Выделение Data2, приведенное выше, дает вам двумерный массив указателей char *, причем указанный двумерный массив размещается в одном фрагменте памяти (10 * 70 * sizeof(char*) байт, или 2800 байт с 32-разрядными указателями). ). У вас нет возможности назначать указатели char ** произвольным местам в памяти, которые у вас есть с одномерным массивом указателей char **.

Также обратите внимание (с учетом приведенных выше объявлений Data и Data2), что компилятор сгенерирует другой код для следующих ссылок на массив:

Data[0][0]
Data2[0][0]

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

char *table0[] = { "Tree", "Bench", "Stream" };
char *table1[] = { "Cow", "Dog", "Cat" };
char *table2[] = { "Banana", "Carrot", "Broccoli" };
char **Data[3];

Data[0] = table0;
Data[1] = table1;
Data[2] = table2;

У вас есть массив указателей на «массив указателей на символ». Если вы теперь выводите значение data[1][1], подумайте об этом так: data[1] возвращает указатель на массив table1. Тогда значение table1[1] равно "Dog".

2 голосов
/ 19 июня 2009

Немного сложно подумать о практическом использовании для массива char **. Особенно один с 70 элементами.

Однако предположим, что я собираюсь запустить 70 программ. Как вы, вероятно, знаете, аргументы программы обычно передаются как char** argv параметр в main() (или char*[] argv, что в сигнатуре функции - то же самое). Поэтому, если бы я хотел сохранить указатели argv для всех этих программ, я бы использовал массив, например Data. Очевидно, мне также понадобится больше памяти в другом месте, чтобы фактические строки и массивы argv занимали, но это начало.

Инициализация массива с {NULL} устанавливает все его элементы в NULL. Это полезное сокращение: вы можете инициализировать массив с {firstelement, secondelement, ...}, но если вы не предоставите достаточно терминов, все остальные будут рассматриваться как 0.

Как и любой другой массив указателей, инициализированных {NULL}, в памяти это выглядит как 70 указателей NULL, расположенных подряд. Там (обычно) нет разницы в памяти между char** и любым другим указателем объекта. Я думаю, что законно написать странную реализацию, в которой есть разница, но не задерживай дыхание, ожидая, чтобы встретить одну.

Итак, разница между 70 NULL char** в строке и 70 NULL char* в строке - это то, что будет на другом конце указателя, если бы они не были NULL. На другом конце char** указатель на символ. На другом конце char* находится символ. В зависимости от того, как он используется, указатель или символ могут быть первыми в массиве.

1 голос
/ 19 июня 2009

У вас есть массив из 70 указателей, каждый из которых указывает на другой указатель, каждый из этих указателей указывает на символ. Что интересно, сами массивы являются указателями, поэтому у вас есть три уровня указателей.

1 голос
/ 19 июня 2009

Это не очень очевидно:

char **Data[70]={NULL};

, но с альтернативным объявлением, например:

char* Data[2][3] = {
  {"Nick", "Tom", "Helen"},
  {"one", "two", "three"}
};

, мы можем легко увидеть, что это двумерный массив строк.

Редактировать: Я использовал Data [2] [3], чтобы показать, что это двумерный массив.Я использовал фиксированный размер для размеров как 2 и 3 только для демонстрации.Конечно, мы можем иметь:

char* Data[][3]={
  {"Nick", "Tom", "Helen"},
  {"one", "two", "three"},
  // ...
};

или char** Data[]

Хорошо, вот что я имею в виду под двумерным массивом:

char** Data[2]={0};

void test()
{
  char* d1[] = {"1", "2"};
  char* d2[] = {"10", "20", "30"};
  Data[0] = d1;
  Data[1] = d2;

  printf("%s\n", Data[0][0]);
  printf("%s\n", Data[0][1]);
  printf("%s\n", Data[1][0]);
  printf("%s\n", Data[1][1]);
  printf("%s\n", Data[1][2]);
}
0 голосов
/ 19 июня 2009

Это, по сути, указатель на указатель на указатели. Тем не менее, поскольку «указатель» - это не что иное, как место в памяти, на самом деле нет особого смысла делать это, чем просто выполнять char * Data [70], кроме того, чтобы сделать очевидным, что каждый char * является указателем на другой char * вместо указателя на символ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...