Подтвердить что ты не робот

В чем разница между определениями 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'; действителен.

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

4b9b3361

Ответ 1

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

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

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

    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
    

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

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

Ответ 2

Макет памяти отличается:

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-указатель-распад, если вам интересно.

Ответ 3

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

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


Чуть иначе можно увидеть что-то вроде этого:

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

+-----+     +--------+
| str | --> | "what" |
+-----+     +--------+

И для второго вы

+--------+--------+--------+
| "what" | "is"   | "this" |
+--------+--------+--------+

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

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

Ответ 4

  • В первом объявлении

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

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

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

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

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

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

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

    [Warning] excess elements in scalar initializer   
    

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

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

  • Вы утверждаете, что "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] указывает на обращение к undefined. Тем не менее вы не можете сделать str[0][0] = 'W'.

  • Отрывок

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

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

Ответ 5

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

char *ptr = "somestring";

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

Следовательно, cnosider эти два утверждения

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 также действителен, потому что память не является readonly.

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


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

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

*str является указателем (str[i] совпадает с *(str+i))

но в этом коде

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

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

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

Ответ 6

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

Массив представляет собой набор элементов того же типа. рассмотрите следующее выражение:

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 *, a 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] - это массив, который является НЕ массивом сам по себе и может быть назначен.

Ответ 7

  • Для начала

    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.

Ответ 8

Случай 1:

Когда я пишу

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

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

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

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

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

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

str - это скаляр и C Стандарты # 6.7.9p11:

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

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

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

Поскольку поведение Undefined соответствует стандарту, нет смысла обсуждать его дальше. Обсуждая Часть 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, массив не является изменяемым значением l. От C Стандарты # 6.3.2.1p1:

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

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

От C Стандарты # 6.3.2.1p3:

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

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

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

Да, это действительно, если i и j являются допустимыми значениями для данного массива str.

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


Предполагая, что в случае 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] - первый символ второй строки)

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

Из C standard # 6.4.5p7:

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


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

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

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

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