В чем разница между определениями массивов char*str={"foo",...} и char str[][5]={"foo",...}?

Случай 1: Когда я пишу

char*str={"what","is","this"};

затем str[i]="newstring"; действует тогда как str[i][j]='j'; является недействительным.

Случай 2: Когда я пишу

char str[][5]={"what","is","this"};

затем str[i]="newstring"; не действует в то время как str[i][j]='J'; является действительным.

Почему это так? Я новичок, который уже очень запутался после прочтения других ответов.

9 ответов

Решение

Прежде всего: предложение: пожалуйста, прочитайте о массивах не указатели, и наоборот!

Тем не менее, чтобы просветить этот конкретный сценарий,

  • В первом случае

    char*str={"what","is","this"};
    

    не делает то, что вы думаете, что делает. Это нарушение ограничения, требующее диагностики от любой соответствующей реализации C, согласно главе§6.7.9/P2:

    Ни один инициализатор не должен пытаться предоставить значение для объекта, не содержащегося в инициализируемом объекте.

    Если вы включите предупреждения, вы бы (по крайней мере) увидели

    предупреждение: лишние элементы в скалярном инициализаторе

      char*str={"what","is","this"};
    

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

    В этом случае компилятор решил сделать это утверждение функционально только таким же, как char*str= "what";

    Так вот str это указатель на char, который указывает на строковый литерал. Вы можете переназначить указатель,

    str="newstring";  //this is valid
    

    но, как заявление

     str[i]="newstring";
    

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

    После этого заявление как

    str[i][j]='J'; // compiler error
    

    синтаксически неверно, так как вы используете подписку Array [] оператор для чего-то, что не является "указателем на полный тип объекта", например

    str[i][j] = ...
          ^^^------------------- cannot use this
    ^^^^^^ --------------------- str[i] is of type 'char', 
                                 not a pointer to be used as the operand for [] operator.
    
  • С другой стороны, во втором случае,

    str это массив массивов. Вы можете изменить отдельные элементы массива,

     str[i][j]='J'; // change individual element, good to go.
    

    но вы не можете назначить массив.

     str[i]="newstring";  // nopes, array type is not an lvalue!!
    

  • Наконец, учитывая, что вы хотели написать (как видно в комментариях)

    char* str[ ] ={"what","is","this"};
    

    в вашем первом случае та же логика для массивов верна. Это делает str массив указателей. Итак, члены массива, назначаемые, так что

    str[i]="newstring";  // just overwrites the previous pointer
    

    совершенно нормально. Однако указатели, которые хранятся как члены массива, являются указателями на строковый литерал, поэтому по той же самой причине, упомянутой выше, вы вызываете неопределенное поведение, когда хотите изменить один из элементов памяти, принадлежащих строковому литералу.

     str[i][j]='j';   //still invalid, as above.
    

Расположение памяти отличается:

char* str[] = {"what", "is", "this"};

    str
+--------+      +-----+
| pointer| ---> |what0|
+--------+      +-----+   +---+
| pointer| -------------> |is0|
+--------+                +---+    +-----+
| pointer| ----------------------> |this0|
+--------+                         +-----+

В этом макете памяти, str это массив указателей на отдельные строки. Обычно эти отдельные строки хранятся в статическом хранилище, и попытка их изменить является ошибкой. В графике я использовал 0 для обозначения завершающих нулевых байтов.

char str[][5] = {"what", "is", "this"};

  str
+-----+
|what0|
+-----+
|is000|
+-----+
|this0|
+-----+

В этом случае, str является смежным 2D массивом символов, расположенных в стеке. Строки копируются в эту область памяти при инициализации массива, и отдельные строки дополняются нулевыми байтами, чтобы придать массиву правильную форму.

Эти две схемы памяти принципиально несовместимы друг с другом. Вы не можете перейти ни к одной функции, которая ожидает указатель на другую. Тем не менее, доступ к отдельным строкам совместим. Когда ты пишешь str[1], вы получите char* до первого символа области памяти, содержащей байты is0строка C

В первом случае ясно, что этот указатель просто загружается из памяти. Во втором случае указатель создается с помощью array-pointer-decay: str[1] на самом деле обозначает массив ровно пять байтов (is000), который сразу же превращается в указатель на свой первый элемент почти во всех контекстах. Однако я считаю, что полное объяснение распада указателя массива выходит за рамки этого ответа. Google массив-указатель-распад, если вам интересно.

С первым вы определяете переменную, которая является указателем на char, который обычно используется как одна строка. Инициализирует указатель для указания на строковый литерал "what", Компилятор также должен жаловаться, что в списке слишком много инициализаторов.

Второе определение делает str массив из трех массивов из пяти char, То есть это массив из трех пятисимвольных строк.


Немного иначе это можно увидеть примерно так:

Для первого случая:

+ ----- + + -------- +
| ул | -> | "что" |
+-----+     +--------+

И для второго у вас есть

+--------+--------+--------+
| "что" | "есть" | "это" |
+--------+--------+--------+

Также обратите внимание, что для первой версии, с указателем на одну строку, выражение str[i] = "newstring" должен также приводить к предупреждениям, так как вы пытаетесь назначить указатель на charэлемент str[i],

Это назначение также недействительно во второй версии, но по другой причине: str[i] это массив (из пяти char элементы), и вы не можете назначить массив, только скопировать в него. Так что вы можете попробовать сделать strcpy(str[i], "newstring") и компилятор не будет жаловаться. Это неправильно, потому что вы пытаетесь скопировать 10 символов (запомните терминатор) в массив из 5 символов, и это будет записывать за пределами, что приведет к неопределенному поведению.

  • В первой декларации

    char *str={"what","is","this"}; 
    

    объявляет str указатель на char и это скаляр. Стандарт говорит, что

    6.7.9 Инициализация (p11):

    Инициализатор для скаляра должен быть одним выражением, необязательно заключенным в фигурные скобки. [...]

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

    char *str = {"what","is","this"}; // three expressions in brace enclosed initializer
    

    это зависит от того, как компиляторы справятся с этим. Обратите внимание, что с остальными инициализаторами возникает ошибка. Подтверждающий компилятор должен выдать диагностическое сообщение.

    [Warning] excess elements in scalar initializer   
    

    5.1.1.3 Диагностика (P1):

    Соответствующая реализация должна генерировать по крайней мере одно диагностическое сообщение (идентифицированное определенным реализацией способом), если блок преобразования или блок предварительной обработки содержит нарушение какого-либо синтаксического правила или ограничения, даже если поведение также явно указано как неопределенное или как реализация. определенный

  • Вы заявляете str[i]="newstring"; действует тогда как str[i][j]='j'; является недействительным. "

    str[i] имеет char типа и может содержать только char тип данных. Назначение "newstring" (который имеет char *) является недействительным. Заявление str[i][j]='j'; недопустим, так как оператор индекса может применяться только к массиву или типу данных указателя.

  • Ты можешь сделать str[i]="newstring"; работая объявив str как массив char *

    char *str[] = {"what","is","this"};
    

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

  • Фрагмент

    char str[][5]={"what","is","this"};
    

    объявлять str как массив массивов char s. str[i] на самом деле является массивом, и так как массивы являются неизменяемыми l-значениями, вы не можете использовать их в качестве левого операнда оператора присваивания. Это делает str[i]="newstring"; недействительным. В то время как str[i][j]='J'; работает, потому что элементы массива могут быть изменены.

Только потому, что вы сказали, что другие ответы сбивают меня с толку, давайте сначала посмотрим, что происходит, на более простом примере

char *ptr = "somestring";

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

Отсюда и эти два утверждения

char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a';     //statement 2 error

Оператор 1 выполняет совершенно допустимую операцию (присваивая 1 указатель другому), но оператор 2 не является допустимой операцией (пытается записать в местоположение только для чтения).

С другой стороны, если мы напишем:

char ptr[] = "somestring";

Здесь ptr на самом деле не указатель, а имя массива (в отличие от указателя он не занимает дополнительное место в памяти). Он выделяет такое же количество байтов, как требуется "somestring" (не только для чтения) и все.

Поэтому рассмотрим те же два утверждения и одно дополнительное утверждение

char *ptr2 = ptr; //statement 1 OK
ptr[1] = 'a';     //statement 2 OK
ptr = "someotherstring" //statement 3 error

Оператор 1 выполняет совершенно корректную операцию (присваивая имя массива указателю, имя массива возвращает адрес 1-го байта), оператор 2 также действителен, поскольку память не только для чтения.

Оператор 3 не является допустимой операцией, потому что здесь ptr не является указателем, он не может указывать на какую-то другую область памяти.


Теперь в этом коде,

char **str={"what","is","this"};

*str это указатель (str[i] такой же как *(str+i))

но в этом коде

char str[][] = {"what", "is", "this"};

str[i] не указатель Это имя массива.

То же самое, что и выше, следует.

Случай 1:

Когда я пишу

char*str={"what","is","this"};

затем str[i]="newstring"; действует тогда как str[i][j]='j'; является недействительным.

Часть II
>> char*str={"what","is","this"};

В этом заявлении str это указатель на char тип. При компиляции вы должны получить предупреждение об этом утверждении:

warning: excess elements in scalar initializer
        char*str={"what","is","this"};
                         ^

Причина предупреждения: вы предоставляете более одного инициализатора для скаляра.
[ Арифметические типы и типы указателей вместе называются скалярными типами. ]

str является скаляром и из стандартов C# 6.7.9p11:

Инициализатор для скаляра должен быть одним выражением, необязательно заключенным в фигурные скобки...

Кроме того, предоставление более одного инициализатора скаляру является неопределенным поведением.
Из стандартов C#J.2 Неопределенное поведение:

Инициализатор для скаляра не является ни одним выражением, ни одним выражением, заключенным в фигурные скобки

Так как это стандартное поведение в соответствии со стандартом, нет смысла обсуждать его дальше. Обсуждая Часть I.II и Часть I.III с предположением - char *str="somestring" просто для лучшего понимания char * тип.
Кажется, вы хотите создать массив указателей на строку. Я добавил краткое описание массива указателей на строку ниже в этом посте, после того, как рассказал об обоих случаях.

Часть I.II
>> then str[i]="newstring"; is valid

Нет, это недействительно
Опять же, компилятор должен выдавать предупреждающее сообщение об этом утверждении из-за несовместимого преобразования.
поскольку str это указатель на char тип. Следовательно, str[i] это персонаж в i проходит мимо объекта, на который указывает str [ str[i] --> *(str + i) ].

"newstring" строковый литерал и строковый литерал распадается на указатель, кроме случаев, когда он используется для инициализации массива типа char * и здесь вы пытаетесь присвоить его char тип. Следовательно, компилятор сообщает об этом как предупреждение.

Часть I.III
>> whereas str[i][j]='j'; is invalid.

Да, это неверно.
[] (оператор индекса) может использоваться с операндами массива или указателя.
str[i] это персонаж и str[i][j] означает, что вы используете [] на char операнд, который является недействительным. Следовательно, компилятор сообщает об этом как об ошибке.

Случай 2:

Когда я пишу

char str[][5]={"what","is","this"};

затем str[i]="newstring"; не действует в то время как str[i][j]='J'; является действительным.

Часть II.I
>> char str[][5]={"what","is","this"};

Это абсолютно правильно. Вот, str это 2D-массив. В зависимости от количества инициализаторов компилятор автоматически установит первое измерение. Просмотр в памяти str[][5] в этом случае будет что-то вроде этого:

         str
         +-+-+-+-+-+
  str[0] |w|h|a|t|0|
         +-+-+-+-+-+
  str[1] |i|s|0|0|0|
         +-+-+-+-+-+
  str[2] |t|h|i|s|0|
         +-+-+-+-+-+

На основании списка инициализаторов соответствующие элементы 2D-массива будут инициализированы, а остальные элементы установлены в 0,

Часть II.II
>> then str[i]="newstring"; is not valid

Да, это не действительно.
str[i] это одномерный массив.
Согласно стандартам C, массив не является изменяемым значением lvalue.
Из стандартов C № 6.3.2.1p1:

Lvalue - это выражение (с типом объекта, отличным от void), которое потенциально обозначает объект;64) если lvalue не обозначает объект при его оценке, поведение не определено. Когда говорят, что объект имеет определенный тип, тип определяется значением l, используемым для обозначения объекта. Модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет константного типа, и если это структура или объединение, не имеет какого-либо члена (включая, рекурсивно, любой член или элемент всех содержащихся агрегатов или объединений) с постоянным типом.

Кроме того, имя массива преобразуется в указатель, указывающий на начальный элемент объекта массива, за исключением случаев, когда он является операндом оператора sizeof, оператора _Alignof или унарного оператора &.

Из стандартов C № 6.3.2.1p3:

За исключением случаев, когда он является операндом оператора sizeof, оператора _Alignof или унарного оператора &, или является строковым литералом, используемым для инициализации массива, выражение, имеющее тип '' массив типа '', преобразуется в выражение с тип '' указатель на тип '', который указывает на начальный элемент объекта массива и не является lvalue.

поскольку str уже инициализирован, и когда вы назначаете какой-то другой строковый литерал i массив str строковый литерал конвертируется в указатель, что делает присваивание несовместимым, поскольку у вас есть lvalue типа char массив и значение типа char *, Следовательно, компилятор сообщает об этом как об ошибке.

Часть II.III
>> whereas str[i][j]='J'; is valid.

Да, это действительно до тех пор, пока i а также j действительные значения для данного массива str,

str[i][j] имеет тип char, так что вы можете назначить ему символ. Остерегайтесь, C не проверяет границы массива, и доступ к массиву за пределами является неопределенным поведением, которое включает в себя - он может случайно сделать именно то, что задумал программист, или ошибка сегментации, или тихая генерация неверных результатов, или что-то может произойти.


Предполагая, что в случае 1 вы хотите создать массив указателей на строку.
Должно быть так:

char *str[]={"what","is","this"};
         ^^

Просмотр в памяти str будет что-то вроде этого:

      str
        +----+    +-+-+-+-+--+
  str[0]|    |--->|w|h|a|t|\0|
        |    |    +-+-+-+-+--+
        +----+    +-+-+--+
  str[1]|    |--->|i|s|\0|
        |    |    +-+-+--+
        +----+    +-+-+-+-+--+
  str[2]|    |--->|t|h|i|s|\0|
        |    |    +-+-+-+-+--+
        +----+

"what", "is" а также "this" строковые литералы.
str[0], str[1] а также str[2] являются указателями на соответствующий строковый литерал, и вы можете сделать так, чтобы они указывали и на другую строку.

Итак, это прекрасно

str[i]="newstring"; 

Если предположить, i 1, так str[1] указатель теперь указывает на строковый литерал "newstring":

        +----+    +-+-+-+-+-+-+-+-+-+--+
  str[1]|    |--->|n|e|w|s|t|r|i|n|g|\0|
        |    |    +-+-+-+-+-+-+-+-+-+--+
        +----+

Но вы не должны делать это:

str[i][j]='j';

(при условии, i=1 а также j=0 , так str[i][j] это первый символ второй строки)

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

Из стандарта C# 6.4.5p7:

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


Дополнительно:

В языке Си нет собственного типа строки. В языке Си строка представляет собой массив символов с нулевым символом в конце. Вы должны знать разницу между массивами и указателями.

Я бы посоветовал вам прочитать следующее для лучшего понимания массивов, указателей, инициализации массива:

  1. Инициализация массива, проверьте это.
  2. Эквивалентность указателей и массивов, проверьте это и это.

Чтобы покончить с путаницей, у вас должно быть правильное понимание указателей, массивов и инициализаторов. Распространенное заблуждение среди начинающих программистов на Си состоит в том, что массив эквивалентен указателю.

Массив - это коллекция элементов одного типа. рассмотрим следующую декларацию:

char arr[10];

Этот массив содержит 10 элементов каждого типа char,

Список инициализаторов может быть использован для инициализации массива удобным способом. Следующее инициализирует элементы массива соответствующими значениями списка инициализатора:

char array[10] = {'a','b','c','d','e','f','g','h','i','\0'};

Массивы не могут быть назначены, поэтому использование списка инициализатора действительно только после объявления массива.

char array[10];
array = {'a','b','c','d','e','f','g','h','i','\0'}; // Invalid...

char array1[10];
char array2[10] = {'a','b','c','d','e','f','g','h','i','\0'};
array1 = array2; // Invalid...; You cannot copy array2 to array1 in this manner.

После объявления массива присваивания членам массива должны выполняться через оператор индексации массива или его эквивалент.

char array[10];
array[0] = 'a';
array[1] = 'b';
.
.
.
array[9] = 'i';
array[10] = '\0';

Циклы являются распространенным и удобным способом присвоения значений членам массива:

char array[10];
int index = 0;
for(char val = 'a'; val <= 'i'; val++) {
    array[index] = val;
    index++;
}
array[index] = '\0';

char Массивы могут быть инициализированы через строковые литералы, которые являются константой с нулевым окончанием char массивы:

char array[10] = "abcdefghi";

Однако следующее недействительно:

char array[10];
array = "abcdefghi"; // As mentioned before, arrays are not assignable

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

Рассмотрим следующую декларацию:

char *ptr;

Это объявляет переменную типа char *, char указатель. То есть указатель, который может указывать на char переменная.

В отличие от массивов, указатели назначаются. Таким образом, действует следующее:

char var;
char *ptr;
ptr = &var; // Perfectly Valid...

Поскольку указатель не является массивом, указателю может быть назначено только одно значение.

char var;
char *ptr = &var; // The address of the variable `var` is stored as a value of the pointer `ptr`

Напомним, что указателю должно быть присвоено одно значение, поэтому следующее недопустимо, поскольку число инициализаторов больше одного:

char *ptr = {'a','b','c','d','\0'};

Это нарушение ограничения, но ваш компилятор может просто назначить 'a' в ptr и игнорировать остальное. Но даже тогда компилятор предупредит вас, потому что литералы символов, такие как 'a' иметь int тип по умолчанию, и несовместим с типом ptr который char *,

Если этот указатель был разыменован во время выполнения, то это приведет к ошибке времени выполнения для доступа к недопустимой памяти, что приведет к сбою программы.

В вашем примере:

char *str = {"what", "is", "this"};

опять же, это нарушение ограничения, но ваш компилятор может назначить строку what в str и игнорировать остальные, и просто отобразить предупреждение:

warning: excess elements in scalar initializer,

Теперь, вот как мы устраняем путаницу в отношении указателей и массивов: в некоторых контекстах массив может распадаться на указатель на первый элемент массива. Таким образом, действует следующее:

char arr[10];
char *ptr = arr;

используя имя массива arr в выражении присваивания как rvalue, массив распадается на указатель на свой первый элемент, что делает предыдущее выражение эквивалентным:

char *ptr = &arr[0];

Помни что arr[0] имеет тип char, а также &arr[0] это его адрес, который имеет тип char *, который совместим с переменной ptr,

Напомним, что строковые литералы являются константами с нулевым символом в конце char массивов, таким образом, следующее выражение также допустимо:

char *ptr = "abcdefghi"; // the array "abcdefghi" decays to a pointer to the first element 'a'

Теперь, в вашем случае, char str[][5] = {"what","is","this"}; это массив из 3 массивов, каждый из которых содержит 5 элементов.

Поскольку массивы не могут быть назначены, str[i] = "newstring"; не действует как str[i] это массив, но str[i][j] = 'j'; действует сstr[i][j] является элементом массива, который сам по себе НЕ является массивом и может быть назначен.

  • Начать с

    char*str={"what","is","this"};
    

    даже не является допустимым кодом C 1), поэтому обсуждение этого вопроса не имеет особого смысла. По какой-то причине компилятор gcc пропускает этот код только с предупреждением. Не игнорируйте предупреждения компилятора. При использовании gcc, всегда компилируйте, используя -std=c11 -pedantic-errors -Wall -Wextra,

  • Что, похоже, делает gcc при обнаружении этого нестандартного кода, так это обращаться с ним так, как если бы вы написали char*str={"what"};, Что, в свою очередь, то же самое, что char*str="what";, Это ни в коем случае не гарантируется языком Си.

  • str[i][j] пытается дважды указать указатель, даже если он имеет только один уровень косвенности, и поэтому вы получаете ошибку компилятора. Это так же мало смысла, как печатать

    int array [3] = {1,2,3}; int x = array[0][0];,

  • Что касается разницы между char* str = ... а также char str[] = ...см. FAQ: В чем разница между char s[] и char *s?,

  • Учитывая char str[][5]={"what","is","this"}; В этом случае создается массив массивов (2D-массив). Самое внутреннее измерение установлено в 5, а самое внешнее измерение устанавливается компилятором автоматически в зависимости от того, сколько инициализаторов предоставил программист. В этом случае 3, поэтому код эквивалентен char[3][5],

  • str[i] дает вам номер массива i в массиве массивов. Вы не можете назначать массивы в C, потому что так устроен язык. Кроме того, было бы неправильно делать это для строки в любом случае, FAQ: Как правильно назначить новое значение строки?


1) Это нарушение ограничения C11 6.7.9/2. Также см. 6.7.9/11.

Случай 1:

char*str={"what","is","this"};

Прежде всего, приведенное выше утверждение недопустимо, внимательно прочитайте предупреждения. str один указатель, он может указывать на single массив символов в то время, чтобы не multiple массив символов

bounty.c:3:2: предупреждение: избыточные элементы в скалярном инициализаторе [включено по умолчанию]

str это char pointer и он хранится в section раздел оперативной памяти, но это contents хранятся в code(Can't modify the content раздел оперативной памяти, потому что str инициализируется с string(in GCC/linux),

как вы заявили str[i]="newstring"; допустимо, тогда как str[i][j]='j'; является недействительным.

str= "new string" не вызывает изменение раздела кода / только для чтения, здесь вы просто назначаете new address в str вот почему это действительно, но

*str='j' или же str[0][0]='j' недопустимо, потому что здесь вы изменяете раздел только для чтения, пытаясь изменить первую букву str,

Случай 2:

char str[][5]={"what","is","this"};

Вот str является 2D массив т.е. str а также str[0],str[1],str[2] сам хранится в stack section из RAM это означает, что вы можете изменить каждый str[i] содержание.

str[i][j]='w'; это верно, потому что вы пытаетесь сложить содержимое раздела, что возможно. но

str[i]= "new string"; это невозможно, потому что str[0] сам массив и массив является константным указателем (не может изменить адрес), вы не можете назначить новый адрес.

Просто в первом случае str="new string" является valid так как str является pointer не array и во втором случае str[0]="new string" является not valid так как str является array не pointer,

Я надеюсь, что это помогает.

Другие вопросы по тегам