Что означает "имя массива" в случае массива указателей char?

В моем коде:

 char *str[] = {"forgs", "do", "not", "die"};
 printf("%d %d", sizeof(str), sizeof(str[0]));  

Я получаю вывод как 12 2, поэтому мои сомнения:

  • Почему существует разница?
  • Оба str и str[0] являются указателями char, правильно?

Ответы

Ответ 1

В большинстве случаев имя массива будет распадаться на значение адреса его первого элемента и с типом, таким же, как указатель на тип элемента. Таким образом, вы ожидаете, что голый str будет иметь значение, равное &str[0], с указателем типа на указатель на char.

Однако это не относится к sizeof. В этом случае имя массива поддерживает свой тип для sizeof, который будет представлять собой массив из 4 указателей на char.

Возвращаемым типом sizeof является size_t. Если у вас есть компилятор C99, вы можете использовать %zu в строке формата для печати значения, возвращаемого sizeof.

Ответ 2

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

Сначала прочитайте: sizeof Оператор

6.5.3.4 Оператор sizeof, 1125:
Когда вы применяете оператор sizeof к типу массива, результатом является общее количество байтов в массиве.

В соответствии с этим, когда sizeof применяется к имени статического идентификатора массива (не выделяется через malloc), результатом является размер в байтах всего массива, а не просто адрес. Это одно из немногих исключений из правила, что имя массива преобразуется/распадается на указатель на первый элемент массива, и это возможно просто потому что фактический размер массива фиксирован и известен во время компиляции, когда оператор sizeof оценивает.

Чтобы лучше понять этот код ниже:

#include<stdio.h>
int main(){
 char a1[6],       // One dimensional
     a2[7][6],     // Two dimensional 
     a3[5][7][6];  // Three dimensional

 printf(" sizeof(a1)   : %lu \n", sizeof(a1));
 printf(" sizeof(a2)   : %lu \n", sizeof(a2));
 printf(" sizeof(a3)   : %lu \n", sizeof(a3));
 printf(" Char         : %lu \n", sizeof(char));
 printf(" Char[6]      : %lu \n", sizeof(char[6]));
 printf(" Char[5][7]   : %lu \n", sizeof(char[7][6]));
 printf(" Char[5][7][6]: %lu \n", sizeof(char[5][7][6]));

 return 1;
} 

Его вывод:

 sizeof(a1)   : 6 
 sizeof(a2)   : 42 
 sizeof(a3)   : 210 
 Char         : 1 
 Char[5]      : 6 
 Char[5][7]   : 42 
 Char[5][7][6]: 210 

Проверьте выше, используя @codepad, размер уведомления char - это один байт, вы заменяете char на int в вышеприведенной программе, то каждый выход будет умножен на sizeof(int) на вашем компьютере.

Разница между char* str[] и char str[][] и то, как оба сохраняются в памяти

Декларация-1: char *str[] = {"forgs", "do", "not", "die"};

В этом объявлении str[] представляет собой массив указателей на char. Каждый индекс str[i] указывает на первый char строк в {"forgs", "do", "not", "die"};.
Логически str должен располагаться в памяти следующим образом:

Array Variable:                Constant Strings:
---------------                -----------------

         str:                       201   202   203   204  205   206
        +--------+                +-----+-----+-----+-----+-----+-----+
 343    |        |= *(str + 0)    | 'f' | 'o' | 'r' | 'g' | 's' | '\0'|
        | str[0] |-------|        +-----+-----+-----+-----+-----+-----+
        | 201    |       +-----------▲
        +--------+                  502   503  504
        |        |                +-----+-----+-----+
 347    | str[1] |= *(str + 1)    | 'd' | 'o' | '\0'|
        | 502    |-------|        +-----+-----+-----+
        +--------+       +-----------▲
        |        |                  43    44    45    46
 351    | 43     |                +-----+-----+-----+-----+
        | str[2] |= *(str + 2)    | 'n' | 'o' | 't' | '\0'|
        |        |-------|        +-----+-----+-----+-----+
        +--------+       +-----------▲
 355    |        |
        | 9002   |                 9002  9003   9004 9005
        | str[3] |                +-----+-----+-----+-----+
        |        |= *(str + 3)    | 'd' | 'i' | 'e' | '\0'|
        +--------+       |        +-----+-----+-----+-----+
                         +-----------▲


Diagram: shows that str[i] Points to first char of each constant string literal. 
Memory address values are assumption.

Примечание. str[] хранится в распределениях продолжения памяти, и каждая строка сохраняется в памяти по случайному адресу (а не в продолжении).

[ОТВЕТ]

В соответствии с Codepad следующий код:

int main(int argc, char **argv){
    char *str[] = {"forgs", "do", "not", "die"};
    printf("sizeof(str): %lu,  sizeof(str[0]): %lu\n", 
            sizeof(str), 
            sizeof(str[0])
    );  
    return 0;
}

Выход:

sizeof(str): 16,  sizeof(str[0]): 4
  • В этом коде str находится массив для 4 char -addresses, где каждый char* имеет размер 4 байта, поэтому в соответствии с вышеприведенным предложением общий размер массива равен 4 * sizeof(char*)= 16 байт.

  • Тип данных str равен char*[4].

  • str[0] - это не что иное, как указатель на char, поэтому его четыре байта. Тип данных str[i] - char*.

(примечание: в некотором системном адресе могут быть 2-байтовые или 8-байтовые)

Относительно выхода следует также прочитать glglgl comment вопрос:

По какой бы архитектуре вы ни были, первое значение должно быть в 4 раза больше второго. На 32-битной машине вы должны получить 16 4, на 64-битной 32. На очень старой или во встроенной системе, вы даже можете получить 8 2, но никогда не 12 2, так как массив содержит 4 элемента одинакового размера

Дополнительные пункты:

  • Поскольку каждый str[i] указывает на char* (и строку) является переменной, str[i] может быть назначен новый адрес строки, например: str[i] = "yournewname"; действителен для i = 0 to < 4.

Еще одно важное замечание:

  • В нашем примере выше str[i], указывающем на постоянный строковый литерал, который нельзя изменить; поэтому str[i][j] = 'A' является недопустимым (мы не можем писать в памяти только для чтения), и это будет ошибкой во время выполнения.
    Но предположим, что если str[i] указывает на простой массив char, то str[i][j] = 'A' может быть допустимым выражением. Рассмотрим следующий код:

      char a[] = "Hello"; // a[] is simple array
      char *str[] = {"forgs", "do", "not", "die"};
      //str[0][4] = 'A'; // is error because writing on read only memory
      str[0] = a;
      str[0][5] = 'A'; // is perfectly valid because str[0] 
                       // points to an array (that is not constant)
    

Проверьте здесь рабочий код: Codepad

Декларация-2: char str[][6] = {"forgs", "do", "not", "die"};:

Здесь str представляет собой двумерный массив символов (где каждая строка равна по размеру) размером 4 * 6. (помните, что здесь вы должны явно указывать значение столбца в объявлении str, но строка 4 из-за количества строк - 4)
В памяти str[][] будет что-то вроде ниже в диаграмме:

                    str
                    +---201---202---203---204---205---206--+
201                 | +-----+-----+-----+-----+-----+-----+|   
str[0] = *(str + 0)--►| 'f' | 'o' | 'r' | 'g' | 's' | '\0'||
207                 | +-----+-----+-----+-----+-----+-----+|
str[1] = *(str + 1)--►| 'd' | 'o' | '\0'| '\0'| '\0'| '\0'||
213                 | +-----+-----+-----+-----+-----+-----+|
str[2] = *(str + 2)--►| 'n' | 'o' | 't' | '\0'| '\0'| '\0'||
219                 | +-----+-----+-----+-----+-----+-----+|
str[3] = *(str + 3)--►| 'd' | 'i' | 'e' | '\0'| '\0'| '\0'||
                    | +-----+-----+-----+-----+-----+-----+|
                    +--------------------------------------+
  In Diagram:                                 
  str[i] = *(str + i) = points to a complete i-row of size = 6 chars. 
  str[i] is an array of 6 chars.

Эта компоновка 2D-массива в памяти называется Row-Major: Многомерный массив в линейной памяти организован таким образом, что строки хранятся один за другим. Это подход, используемый языком программирования C.

Обратите внимание на различия в обеих диаграммах.

  • Во втором случае полный двухмерный массив char выделяется в памяти продолжения.
  • Для любого значения i = 0 to 2, str[i] и str[i + 1] значение отличается на 6 байтов (то есть равно одной строке).
  • Двойная граничная линия на этой диаграмме означает, что str представляет полный 6 * 4 = 24 символа.

Теперь рассмотрим аналогичный код, который вы разместили в своем вопросе для 2-мерного массива char, проверьте Codepad:

int main(int argc, char **argv){
    char str[][6] = {"forgs", "do", "not", "die"};
    printf("sizeof(str): %lu,  sizeof(str[0]): %lu\n", 
            sizeof(str), 
            sizeof(str[0])
    );
    return 0;
}

Выход:

sizeof(str): 24,  sizeof(str[0]): 6

В соответствии с обработкой оператора sizeof массивом, при применении размера массива 2-мерного размера должен возвращать весь размер массива, который равен 24 байтам.

  • Как нам известно, оператор sizeof возвращает размер всего массива при применении имени массива. Таким образом, для sizeof(str) он возвращает = 24, который является размером полного массива 2D char, состоит из 24 символов (6-cols * 4 строки).

  • В этом объявлении тип str равен char[4][6].

  • Еще один интересный момент: str[i] представляет чаты массивов, а тип - char[6]. И sizeof(str[0]) - полный размер массива = 6 (длина строки).

Дополнительные пункты:

  • Во втором объявлении str[i][j] не является постоянным, и его содержимое может быть изменено, например. str[i][j] = 'A' - действительная операция.

  • str[i] является именем char массив типа char[6] является константой и присваивается str[i], например. str[i] = "newstring" - незаконная операция (заражение будет ошибкой компиляции).

Еще одно важное различие между двумя объявлениями:

В Декларация-1: char *str[] = {"forgs", "do", "not", "die"}; тип &str - это char*(*)[4], его адрес массива указателей char.

В Декларация-2: char str[][6] = {"forgs", "do", "not", "die"}; тип &str - это char(*)[4][6], его адрес 2-D char массив из 4 строк и 6 столбцов.

Если вы хотите прочитать подобное описание для 1-D массива: Что возвращает sizeof(&array)?

Ответ 3

Это 16 4 на моем компьютере, и я могу это объяснить: str - это массив char*, поэтому sizeof(str)==sizeof(char*)*4

Я не знаю, почему вы получаете 12 2, хотя.

Ответ 4

Два указателя разные. str - это array of char pointers, в вашем примере это (char*[4]), а str[0] - char pointer.

Первый sizeof возвращает размер четырех указателей char, который содержит, а второй возвращает sizeof char*.
В моих тестах результаты:

sizeof(str[0]) = 4   // = sizeof(char*)

sizeof(str) = 16  
            = sizeof(str[0]) + sizeof(str[1]) + sizeof(str[2]) + sizeof(str[3])
            = 4 * sizeof(char*)  
            = 4 * 4
            = 16