关于C#:char * str = {“foo”,…}和char str [] [5] = {“foo”,…}数组定义有什么区别?

What is the difference between char*str={“foo”,…} and char str[][5]={“foo”,…} array definitions?

案例1:我写的时候

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

那么str[i]="newstring";有效,而str[i][j]='j';无效。

案例2:我写的时候

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

那么str[i]="newstring";无效,而str[i][j]='j';有效。

为什么会这样? 我是一个初学者,在阅读其他答案后已经非常困惑。


首先:一个建议:请阅读有关数组不是指针,反之亦然!

也就是说,为了启发这种特殊情况,

  • 在第一种情况下,

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

    不会做你认为它做的事情。这是一种约束违规,需要根据章节§6.7.9/ P2从任何符合要求的C实现进行诊断:

    No initializer shall attempt to provide a value for an object not contained within the entity
    being initialized.

    如果你启用警告,你(至少)会看到

    warning: excess elements in scalar initializer

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

    但是,打开严格一致性的(ny)编译器应该拒绝编译代码。如果编译器选择编译并生成二进制文件,那么行为就不会影响C语言的定义范围,这取决于编译器的实现(因此可以有很大的不同)。

    在这种情况下,编译器决定此语句在功能上仅与char*str="what";相同

    所以,这里str是一个指向char的指针,它指向一个字符串文字。
    您可以重新分配指针,

    1
    str="newstring";  //this is valid

    但是,像一个声明

    1
     str[i]="newstring";

    将无效,因为在这里,尝试转换指针类型并将其存储到char类型中,其中类型不兼容。在这种情况下,编译器应该抛出有关无效转换的警告。

    此后,声明如

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

    在语法上是无效的,因为你在不是"指向完整对象类型的指针"的东西上使用了Array subscripting []运算符,就像

    1
    2
    3
    4
    str[i][j] = ...
          ^^^------------------- cannot use this
    ^^^^^^ --------------------- str[i] is of type 'char',
                                 not a pointer to be used as the operand for [] operator.
  • 另一方面,在第二种情况下,

    str是一个数组数组。你可以改变单个数组元素,

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

    但你不能分配给一个数组。

    1
     str[i]="newstring";  // nopes, array type is not an lvalue!!
  • 最后,考虑你打算写(如评论中所示)

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

    在第一种情况下,数组的逻辑相同。这使得str成为一个指针数组。所以,数组成员是可分配的,所以,

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

    完全没问题。但是,作为数组成员存储的指针是指向字符串文字的指针,因此出于上述原因,当您要修改属于字符串文字的内存中的一个元素时,调用未定义的行为。

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


内存布局不同:

1
2
3
4
5
6
7
8
9
10
char* str[] = {"what","is","this"};

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

在此内存布局中,str是指向各个字符串的指针数组。通常,这些单独的字符串将驻留在静态存储中,尝试修改它们是错误的。在图中,我使用0来表示终止空字节。

1
2
3
4
5
6
7
8
9
10
char str[][5] = {"what","is","this"};

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

在这种情况下,str是位于堆栈上的连续2D字符数组。在初始化数组时,字符串被复制到该存储区中,并且用零字节填充各个字符串以使该数组具有规则形状。

这两种内存布局从根本上是不相容的。您不能将任何一个传递给期望指向另一个的函数。但是,访问单个字符串是兼容的。当你写str[1]时,你得到一个char*到包含字节is0的内存区域的第一个字符,即一个C字符串。

在第一种情况下,很明显这个指针只是从内存加载。在第二种情况下,指针是通过数组指针衰减创建的:str[1]实际上表示一个恰好五个字节(is000)的数组,它几乎在所有上下文中立即衰减为指向其第一个元素的指针。但是,我认为对数组指针衰减的完整解释超出了这个答案的范围。如果你好奇,谷歌数组指针会衰减。


首先,您定义一个变量,该变量是指向char的指针,该变量通常仅用作单个字符串。它初始化指针以指向字符串文字"what"。编译器还应该抱怨列表中有太多的初始值设定项。

第二个定义使str成为由五个char组成的三个数组的数组。也就是说,它是由三个五个字符的字符串组成的数组。

稍微不同,可以看到这样的事情:

对于第一种情况:

1
2
3
+-----+     +--------+
| str | --> |"what" |
+-----+     +--------+

而你的第二个

1
2
3
+--------+--------+--------+
|"what" |"is"   |"this" |
+--------+--------+--------+

另请注意,对于第一个版本,如果指向单个字符串,则表达式str[i] ="newstring"也会导致警告,因为您尝试将指针指定给单个char元素str[i]

该分配在第二个版本中也是无效的,但出于另一个原因:str[i]是一个数组(由五个char元素组成),您不能分配给一个数组,只能复制到它。所以你可以尝试做strcpy(str[i],"newstring"),编译器不会抱怨。但这是错误的,因为你试图将10个字符(记住终结符)复制到5个字符的数组中,这将写出超出界限导致未定义的行为。


  • 在第一个声明中

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

    声明str指向char的指针并且是标量。标准说

    6.7.9初始化(p11):

    The initializer for a scalar shall be a single expression, optionally enclosed in braces. [...]

    也就是说,标量类型可以使用括号封闭的初始化器,但只有一个表达式,但是如果有的话

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

    编译器是如何处理它的。请注意,其余初始化程序会发生什么错误。确认编制者应给出诊断信息。

    1
    [Warning] excess elements in scalar initializer

    5.1.1.3诊断(P1):

    A conforming implementation shall produce at least one diagnostic message (identified in an implementation-defined manner) if a preprocessing translation unit or translation unit contains a violation of any syntax rule or constraint, even if the behavior is also explicitly specified as undefined or implementation-defined

  • 您声称"str[i]="newstring";有效,而str[i][j]='j';无效。"

    str[i]属于char类型,只能包含char数据类型。分配"newstring"(char *)无效。语句str[i][j]='j';无效,因为下标运算符只能应用于数组或指针数据类型。

  • 您可以通过将str声明为char *的数组来使str[i]="newstring";工作

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

    在这种情况下,str[i]char *类型,并且可以为其分配字符串文字,但修改字符串文字str[i]指向将调用未定义的行为。那说你不能做str[0][0] = 'W'

  • 片段

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

    str声明为char的数组数组。 str[i]实际上是一个数组,并且数组是不可修改的左值,因此您不能将它们用作赋值运算符的左操作数。这使str[i]="newstring";无效。虽然str[i][j]='j';有效,因为可以修改数组的元素。


仅仅因为你说其他答案令我困惑,让我们先看一个更简单的例子来看看发生了什么

1
char *ptr ="somestring";

这里"somestring"是一个字符串文字,存储在存储器的只读数据部分。 ptr是指针(与代码的同一部分中的其他变量一样分配),指向该分配的内存的第一个字节。

因此cnosider这两个陈述

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

语句1执行完全有效的操作(将1指针指向另一个),但语句2不是有效操作(尝试写入只读位置)。

另一方面,如果我们写:

1
char ptr[] ="somestring";

这里ptr实际上不是指针,而是数组的名称(与指针不同,它不占用内存中的额外空间)。它分配"somestring"(非只读)所需的相同字节数,就是这样。

因此,请考虑相同的两个陈述和一个额外的陈述

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

语句1执行完全有效的操作(将数组名称指定给指针,数组名称返回第1个字节的地址),语句2也有效,因为内存不是只读的。

语句3不是有效的操作,因为这里ptr不是指针,它不能指向其他一些内存位置。

现在在这段代码中,

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

*str是一个指针(str[i]*(str+i)相同)

但是在这段代码中

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

str[i]不是指针。它是数组的名称。

与上述相同的是。


情况1 :

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

首先上述声明无效,请正确阅读警告。 str是单指针,它可以指向single char数组,而不是multiple char数组。

bounty.c:3:2:警告:标量初始值设定项中的多余元素[默认启用]

strchar pointer,它存储在RAM的section部分,但它的contents存储在RAM的code(Can't modify the content部分,因为strstring(in GCC/linux)初始化。

正如你所说str [i] ="newstring";是有效的,而str [i] [j] ='j';是无效的。

str="new string"不会导致修改代码/只读部分,这里只是将new address分配给str这就是为什么它有效但是

*str='j'str[0][0]='j'无效,因为您在此处修改只读部分,尝试更改str的第一个字母。

案例2:

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

这里str2D数组,即strstr[0],str[1],str[2]本身存储在RAM RAM中,这意味着您可以更改每个str[i]内容。

str[i][j]='w';它是有效的,因为您正在尝试堆叠可能的部分内容。但

str[i]="new string";这是不可能的,因为str[0]本身的数组和数组是const指针(不能改变地址),你不能分配新的地址。

简单地说,在第一种情况下,str="new string"valid,因为strpointer,而不是array,而在第二种情况下,str[0]="new string"not valid,因为strarray而不是pointer

我希望它有所帮助。


情况1:

当我写作

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

那么str[i]="newstring";有效,而str[i][j]='j';无效。

第一部分.I
>> char*str={"what","is","this"};

在此语句中,str是指向char类型的指针。
编译时,您必须在此语句中收到警告消息:

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

警告的原因是 - 您为标量提供了多个初始值设定项。
[算术类型和指针类型统称为标量类型。]

str是一个标量,来自C标准#6.7.9p11:

The initializer for a scalar shall be a single expression, optionally enclosed in braces. ..

Ok.

此外,为标量提供多个初始化程序是未定义的行为。
来自C标准#J.2未定义的行为:

The initializer for a scalar is neither a single expression nor a single expression enclosed in braces

Ok.

由于它是按照标准的未定义行为,因此没有必要进一步讨论它。讨论第I.II部分和第III部分的假设 - char *str="somestring",只是为了更好地理解char *类型。
似乎你想要创建一个指向字符串的指针数组。在谈到这两个案例之后,我在这篇文章的下面添加了一个关于字符串指针数组的简要说明。

第一部分.II
>> then str[i]="newstring"; is valid

不,这是无效的。
同样,由于转换不兼容,编译器必须在此语句上发出警告消息。
由于str是指向char类型的指针。因此,str[i]str [str[i] --> *(str + i)]所指向的对象之后i处的字符。

"newstring"是字符串文字,字符串文字会衰减为指针,除非用于初始化类型为char *的数组,此处您尝试将其指定为char类型。因此编译器将其报告为警告。

第III部分
>> whereas str[i][j]='j'; is invalid.

是的,这是无效的。
[](下标运算符)可以与数组或指针操作数一起使用。
str[i]是一个字符,str[i][j]表示您在char操作数上使用的[]是无效的。因此编译器将其报告为错误。

案例2:

当我写作

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

那么str[i]="newstring";无效,而str[i][j]='j';有效。

第二部分.I
>> char str[][5]={"what","is","this"};

这绝对是正确的。
这里,str是2D阵列。根据初始化程序的数量,编译器将自动设置第一个维度。
在这种情况下,str[][5]的内存中视图将是这样的:

1
2
3
4
5
6
7
8
         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标准,数组不是可修改的左值。
来自C标准#6.3.2.1p1:

An lvalue is an expression (with an object type other than void) that potentially designates an object;64) if an lvalue does not designate an object when it is evaluated, the behavior is undefined. When an object is said to have a particular type, the type is specified by the lvalue used to designate the object. A modifiable lvalue is an lvalue that does not have array type, does not have an incomplete type, does not have a const- qualified type, and if it is a structure or union, does not have any member (including, recursively, any member or element of all contained aggregates or unions) with a const- qualified type.

Ok.

此外,数组名称转换为指向数组对象的初始元素的指针,除非它是sizeof运算符,_Alignof运算符或一元&运算符的操作数。运营商。

来自C标准#6.3.2.1p3:

Except when it is the operand of the sizeof operator, the _Alignof operator, or the unary & operator, or is a string literal used to initialize an array, an expression that has type ''array of type'' is converted to an expression with type ''pointer to type'' that points to the initial element of the array object and is not an lvalue.

Ok.

由于str已经初始化,并且当您将其他字符串文字分配给str str的数组时,字符串文字将转换为指针,这使得赋值不兼容,因为您具有类型char数组和rvalue的左值类型char *。因此编译器将其报告为错误。

第II.III部分
>> whereas str[i][j]='J'; is valid.

是的,只要ij是给定数组str的有效值,这就有效。

str[i][j]的类型为char,因此您可以为其指定一个字符。
请注意,C不检查数组边界并且访问数组越界是未定义的行为,包括 - 它可能偶然地完成程序员的意图或分段错误或者无声地生成错误结果或任何事情都可能发生。

假设在案例1中,您想要创建一个指向字符串的指针数组。
它应该是这样的:

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

str的内存中视图将是这样的:

1
2
3
4
5
6
7
8
9
10
11
      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]是指向相应字符串文字的指针,您也可以将它们指向其他字符串。

所以,这非常好:

1
str[i]="newstring";

假设i为1,那么str[1]指针现在指向字符串文字"newstring"

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

但你不应该这样做:

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

(假设i=1j=0,所以str[i][j]是第二个字符串的第一个字符)

根据标准尝试修改字符串文字导致未定义的行为,因为它们可能存储在只读存储中或与其他字符串文字组合。

从C标准#6.4.5p7:

It is unspecified whether these arrays are distinct provided their elements have the appropriate values. If the program attempts to modify such an array, the behavior is undefined.

Ok.

额外:

C语言中没有本机字符串类型。 在C语言中,字符串是以空字符结尾的字符数组。 你应该知道数组和指针之间的区别。

我建议你阅读以下内容,以便更好地理解数组,指针,数组初始化:

  • 数组初始化,检查一下。
  • 指针和数组的等价,检查这个和这个。
  • 好。


    • 首先

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

      甚至不是有效的C代码1),所以讨论它不是很有意义。出于某种原因,gcc编译器只允许此代码发出警告。不要忽略编译器警告。使用gcc时,请确保始终使用-std=c11 -pedantic-errors -Wall -Wextra进行编译。

    • 当遇到这个非标准代码时,gcc似乎要做的就是将其视为编写char*str={"what"};。这又与char*str="what";相同。这绝不是C语言的保证。

    • str[i][j]尝试间接指针两次,即使它只有一个间接级别,因此会出现编译器错误。它打字的意义不大

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

    • 至于char* str = ...char str[] = ...之间的区别,请参阅常见问题: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。


    要消除混淆,您必须正确理解指针,数组和初始化器。
    C编程初学者中常见的误解是数组等同于指针。

    数组是相同类型的项的集合。考虑以下声明:

    1
    char arr[10];

    该数组包含10个元素,每个元素的类型为char

    初始化列表可用于以方便的方式初始化阵列。下面使用初始化列表的相应值初始化数组元素:

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

    数组不可分配,因此初始化程序列表的使用仅在数组声明时有效。

    1
    2
    char array[10];
    array = {'a','b','c','d','e','f','g','h','i','\0'}; // Invalid...
    1
    2
    3
    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.

    在声明数组之后,对数组成员的赋值必须通过数组索引运算符或其等价运算符。

    1
    2
    3
    4
    5
    6
    7
    8
    char array[10];
    array[0] = 'a';
    array[1] = 'b';
    .
    .
    .
    array[9] = 'i';
    array[10] = '\0';

    循环是为数组成员赋值的常用且方便的方法:

    1
    2
    3
    4
    5
    6
    7
    char array[10];
    int index = 0;
    for(char val = 'a'; val <= 'i'; val++) {
        array[index] = val;
        index++;
    }
    array[index] = '\0';

    char数组可以通过字符串文字初始化,字符串文字是常量空终止char数组:

    1
    char array[10] ="abcdefghi";

    但是以下内容无效:

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

    现在,让我们来指点一下......
    指针是可以存储另一个变量的地址的变量,通常是相同类型的变量。

    请考虑以下声明:

    1
    char *ptr;

    这声明了一个char *类型的变量,一个char指针。也就是说,指针可能指向char变量。

    与数组不同,指针是可分配的。因此以下内容有效:

    1
    2
    3
    char var;
    char *ptr;
    ptr = &var; // Perfectly Valid...

    由于指针不是数组,因此指针只能分配一个值。

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

    回想一下,必须为指针分配一个值,因此以下内容无效,因为初始值设定项的数量不止一个:

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

    这是一个约束违规,但您的编译器可能只是将'a'分配给ptr而忽略其余的。但即使这样,编译器也会发出警告,因为'a'等字符文字默认情况下具有int类型,并且与char *的类型char *不兼容。

    如果在运行时已取消引用此指针,则会导致访问无效内存的运行时错误,从而导致程序崩溃。

    在你的例子中:

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

    再次,这是一个约束违规,但您的编译器可能会将字符串what分配给str并忽略其余的,并只显示一个警告:

    warning: excess elements in scalar initializer.

    现在,这是我们如何消除关于指针和数组的混淆:
    在某些上下文中,数组可能会衰减为指向数组第一个元素的指针。因此以下内容有效:

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

    通过在赋值表达式中使用数组名称arr作为rvalue,数组衰减到指向它的第一个元素的指针,这使得前一个表达式等效于:

    1
    char *ptr = &arr[0];

    请记住,arr[0]的类型为char&arr[0]是其char *类型的地址,它与变量ptr兼容。

    回想一下,字符串文字是常量的空终止char数组,因此以下表达式也是有效的:

    1
    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]是一个数组元素,它本身不是一个数组,并且是可赋值的。