Учитывая, что строка C представляет собой массив символов с нулевым завершением:
const char str1[] = {"abc"};
const char str2[] = {'a', 'b', 'c', '\0'};
const char str3[] = {97, 98, 99, '\0'};
одинаковы.
Так почему же можно сгенерировать такой массив строк:
const char * str_arr[] = {"abc", "ABCD"};
const char * str_arr[] = { str1, str2, str3 };
а не так? :
const char * str_arr[] = { {'a', 'b', 'c', '\0'}, {'A', 'B', 'C', 'D', '\0'} };
fatal error: excess elements in scalar initializer
{'a', 'b', 'c', '\0'} НЕ является массивом. Это список инициализаторов.
const char * str = {'a', 'b', 'c', '\0'}; смысла нет. Как вы думаете, почему const char * str_arr[] = { {'a', 'b', 'c', '\0'}, {'A', 'B', 'C', 'D', '\0'} }; делает?
У вас также есть противоположное, const char str[3] = "abc";, который представляет собой массив символов, который не является строкой (потому что он не завершается нулем.
@BoP Я не думаю, что это правильно, нулевое завершение добавляется автоматически. Он даже не компилируется: fatal error: initializer-string for char array is too long - что решается заменой 3 на 4.
@John - Это сообщение об ошибке похоже на то, что clang дал бы вам для C++, но не для C. Разные языки.



Вы на самом деле можете, если примените их к const char []:
const char * str_arr[] = { (const char []){'a', 'b', 'c', '\0'}, (const char []){'A', 'B', 'C', 'D', '\0'} };
Вы не можете инициализировать тип const char * массивом char (потому что где указатель?), но вы можете инициализировать этим тип const char[], и он распадется на const char *.
Это не приведение, это составной литерал и связанный с ним список инициализаторов. Необходимо соблюдать осторожность в отношении срока службы этих объектов.
В этом ответе не хватает нюансов. Конечно, вы можете использовать массив для инициализации указателя, например, char a[] = "abc"; char *ap = a;. Здесь a — это массив (инициализированный строковым литералом), используемый для инициализации ap (инициализированный указателем на первый элемент a). { 'a', 'b', 'c', '\0' } не является массивом в коде OP или в этом ответе; (const char[]){ 'a', 'b', 'c', '\0' } является массивом в силу того, что он является составным литералом, но массив не «приводится» и даже не используется для инициализации анонимного массива. { 'a', 'b', 'c', '\0\ } — это просто список инициализаторов.
const char str1[] = {"abc"};
const char str2[] = {'a', 'b', 'c', '\0'};
const char str3[] = {97, 98, 99, '\0'};
Они работают из-за правил C 2018 6.7.9 о том, как обрабатываются инициализаторы:
const char str1[] = {"abc"}; дается строковый литерал для инициализации массива const char. В 6.7.9 14 говорится: «Массив символьного типа может быть инициализирован строковым литералом символов или строковым литералом UTF–8, необязательно заключенным в фигурные скобки. Последовательные байты строкового литерала (включая завершающий нулевой символ, если есть место или если размер массива неизвестен) инициализируют элементы массива».const char str2[] = {'a', 'b', 'c', '\0'}; для инициализации массива дается список int значений (символьные константы имеют тип int). 6.7.9 17 говорит нам, что они используются для инициализации подобъектов массива (его элементов) по порядку.const char * str_arr[] = {"abc", "ABCD"};
const char * str_arr[] = { str1, str2, str3 };
Для этих:
const char * str_arr[] = {"abc", "ABCD"}; список строковых литералов используется для инициализации массива указателей. Обратите внимание, что это не строковый литерал, заключенный в фигурные скобки; это два строковых литерала, поэтому это не соответствует приведенному выше правилу. Не существует специального правила для строковых литералов, используемых для инициализации массива указателей вместо массива символьного типа, поэтому он обрабатывается с использованием других правил C, а два строковых литерала инициализируют два элемента массива. Строковый литерал обозначает массив символов. 6.3.2.1 3 говорит, что когда массив не является операндом sizeof, операндом унарного & или строковым литералом, используемым для инициализации массива, он преобразуется в указатель на его первый элемент. Таким образом, каждый из этих двух массивов, обозначенных строковыми литералами, преобразуется в указатель на его первый элемент, и эти указатели становятся начальными значениями str_arr.const char * str_arr[] = { str1, str2, str3 };, str1, str2 и str3 это массивы. Как и выше, они автоматически преобразуются в указатели на свои первые элементы, и эти указатели используются для инициализации str_arr.const char * str_arr[] = { {'a', 'b', 'c', '\0'}, {'A', 'B', 'C', 'D', '\0'} };
Для этого каждый элемент str_arr является const char *, а предлагаемый для него инициализатор — {'a', 'b', 'c', '\0'}. В C нет правила, согласно которому const char * может быть инициализирован списком значений int в фигурных скобках. Когда {'a', 'b', 'c', '\0'} появляется в инициализаторах, он не имеет особого значения как массив. Это просто список значений. Способ обработки этого списка значений зависит от контекста инициализации. Если бы нужно было заполнить массив, значения в списке можно было бы использовать для заполнения элементов массива. Но нет ни массива, ни правила, которое говорит, что этот список значений превращается в массив и создается указатель на этот массив.
Спасибо за очень полезный и исчерпывающий ответ. Мне потребовалось некоторое время, чтобы понять это, и мне становится ясно, что иногда я борюсь с указателями (и их синтаксисом), как только это выходит за рамки одного указателя на одну переменную. Также мне были непонятны тонкости строковых литералов и инициализации. Вероятно, мне следует получить хороший справочник (книгу или веб-сайт), чтобы продвинуться в C, но я еще не смог найти его на нужном уровне...
Как массив символов может превратиться в массив указателей на символы?