char ** и указатели разыменования - PullRequest
11 голосов
/ 14 февраля 2011

Я хотел бы покончить с путаницей с помощью char **

Когда однажды создаю массив массивов символов (строк), как char ** на самом деле это делает?

я получаю этоchar * - это указатель на символ, а char *array[] - это массив указателей на символ, но что именно делает char ** и как он это делает?

Также, когда я слышу слово, разыменовывает егозаставляет меня думать, что указатель удален, что именно означает разыменование указателя?Изменение значения, на которое указывает указатель?

Спасибо

Ответы [ 4 ]

13 голосов
/ 14 февраля 2011

«Разыменование» указателя означает доступ к значению, на которое указывает указатель.Предположим следующие объявления:

int a = 10;
int *p = &a;

Вот гипотетическая карта памяти двух переменных:

Item      Address      0x00  0x01  0x02  0x03
----      -------      ----  ----  ----  ----
   a      0x80001000   0x00  0x00  0x00  0x0A
   p      0x80001004   0x80  0x00  0x10  0x00

a содержит целочисленное значение 10. p содержит адрес a (0x80001000).Если мы хотим получить доступ к содержимому от a до p, мы разыменовываем p с помощью оператора косвенности *.Таким образом, выражение *p эквивалентно выражению a.Если мы написали

 *p = 16;

, это то же самое, что написать

  a = 16;

Вот небольшой фрагмент кода, показывающий, как использовать объект типа char ** для создания массива строк:

#include <stdlib.h>
#define N      20  // For this example, we will allocate 20 strings
#define LENGTH 10  // of 10 characters each (not counting 0 terminator)
...
char **arr = malloc(sizeof *arr * N); 
if (arr)
{
  size_t i;
  for (i = 0; i < N; i++)
  {
    arr[i] = malloc(sizeof *arr[i] * (LENGTH + 1)); 
    strcpy(arr[i], "          "); 
  }
}

Проходя по этой строке построчно,

char **arr = malloc(sizeof *arr * N); 

выделяет блок из N элементов, каждый из которых достаточно большой для хранения указателя на символ (sizeof *arr == sizeof (char *)начиная с типа *arr == char *), и присваивает результирующее значение указателя arr.IOW, arr указывает на первый указатель на char, следовательно, тип char **.Обратите внимание, что если вы разделите объявление и вызов функции, это будет выглядеть как

char **arr;
...
arr = malloc(sizeof *arr * N);

Мы хотим присвоить результат malloc для arr, а не для того, что arr очковдо .

if (arr)

Может произойти сбой malloc, поэтому мы хотим проверить результат перед его использованием.В случае сбоя malloc возвращается значение указателя NULL.

{
  size_t i;
  for (i = 0; i < N; i++)
  {
    arr[i] = malloc(sizeof *arr[i] * (LENGTH + 1));

Для каждого символьного указателя arr[i] мы выделяем блок памяти, достаточно большой для элементов LENGTH + 1, каждый из которых достаточно большой, чтобы содержать значение char (sizeof *arr[i] == sizeof (char), поскольку тип *arr[i] == char; обратите внимание, что sizeof (char) всегда 1) и присвойте результат arr[i].

Поскольку мы выделяем каждую строку отдельным вызовом malloc, маловероятно, чтобы они были смежными в памяти.Вот еще одна карта памяти, показывающая возможный результат кода выше:

Item         Address        0x00  0x01  0x02  0x03
----         -------        ----  ----  ----  ----
 arr         0x80001000     0xA0  0xCC  0x00  0x00  
             ...
 arr[0]      0xA0CC0000     0xA0  0xCC  0x20  0x00     
 arr[1]      0xA0CC0004     0xA0  0xCC  0x20  0x40
 arr[2]      0xA0CC0008     0xA0  0xCC  0x21  0x28
             ...
 arr[19]     0xA0CC0014     0xA0  0xCC  0x23  0x10
             ...
 arr[0][0]   0xA0CC2000     ' '   ' '   ' '   ' '
 arr[0][4]   0xA0CC2004     ' '   ' '   ' '   ' '
 arr[0][8]   0xA0CC2008     ' '   ' '   0x00  0x??
             ...
 arr[1][0]   0xA0CC2040     ' '   ' '   ' '   ' '
 arr[1][4]   0xA0CC2044     ' '   ' '   ' '   ' '
 arr[1][8]   0xA0CC2048     ' '   ' '   0x00  0x??
             ...
8 голосов
/ 14 февраля 2011

Указатель - это тип, который содержит адрес для значения вместо того, чтобы содержать действительное значение.

Таким образом, в случае char * p, после выделения, p будет содержать адрес A. Разыменованиеэтот указатель означает доступ к значению, которое хранится по адресу A. Причина, по которой вы можете хранить строки в символе *, заключается в том, что выделенная память является смежной.Итак, A - это адрес, в котором хранится первый символ, A + 1 - это адрес, в котором хранится второй символ и т. Д.

В случае char ** pp он хранит адрес char *,Назовите этот адрес B. Таким образом, разыменование pp означает доступ к значению по адресу B, который является символом *, который содержит строку.Таким же образом, B + 1 (на самом деле B + sizeof (char *)) хранит следующее значение, которое является другой строкой.

Разыменование pp дважды (т.е. ** pp) означает, что вы сначала получаете доступ к значению вадрес B, который, например, является A, а затем разыменовываем его еще раз, чтобы получить значение по адресу A, который является некоторым символом.

3 голосов
/ 14 февраля 2011

Диаграммы стоят 1000 слов.Взгляните здесь

char, char * и char ** - это просто типы, описывающие, что содержит переменная (область памяти).

Использование разыменования как * переменной на самом делеговорит, что обрабатывать значение в переменной как адрес памяти и фактически возвращать значение по этому адресу.Это косвенность.

** переменная - это просто два уровня косвенности.то есть значение в переменной является адресом памяти еще одного адреса памяти данных, которые будут возвращены.

Адреса обычно поступают из адреса оператора & или из функции / оператора выделения памяти, подобногоnew

3 голосов
/ 14 февраля 2011

Разыменование указателя означает доступ к значению, на которое указывает указатель . Например,

char c = 'c'; // This is a primitive value. You cannot dereference it.
char* p1 = &c; // A pointer to the address of c
char** p2 = &p1; // A pointer to the address of p1
/* Now, the following is true:
*p1 == c, i.e. dereferencing p1 allows us to read from/write to c.
*p2 == p1
**p2 == *(*p2) == *p1 = c - dereferencing p2 twice is c, too */

Причина, по которой вы используете указатель на c вместо c напрямую, состоит в том, что указатель позволяет вам получить доступ к более чем 1 значению. Возьмите этот пример:

char[4] str;
char c0 = 'a', c1 = 'b', c3 = 'c', c4 = '\0';
str[0] = c0; str[1] = c1; str[2] = c2; str[3] = c3;
str = "abc"; // Same as the above line

Теперь предположим, что нам нужен второй символ. Мы могли бы получить к нему доступ c1. Но, как вы можете видеть, это обозначение действительно громоздко. Кроме того, если мы будем читать строку из файла, а не записывать ее, нам придется делать сложные вещи. Вместо этого мы просто пишем

str[1] /* or */ *(str+1)

Обратите внимание, что первый элемент имеет индекс 0, второй - вот почему мы используем 1 здесь. A char** увеличивает это до одиннадцати - у нас есть массив массивов символов. Предположим, у нас есть такой массив, назовем его input, и нам нужно выяснить длину всех строк в нем. Вот как мы это сделаем:

int lensum(char **input) {
    int res = 0;
    while (*input) { // This loops as long as the value input points to is not 0.
        char* p = *input; // Make a copy of the value input currently points to
        while (*p != '\0') { // Loop while the copy does not point to a char '\0'
            res += 1; // We found a character
            p++; // Check next character in the next iteration
        }
        input++; // Check next string in the next iteration
    }
    return res;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...