char*str={"foo",...} 和 char str[][5]={"foo",...} 数组定义有什么区别?

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

案例一:当我写

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

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

案例二:当我写

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

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

为什么会这样?我是初学者,看完其他答案已经很迷茫了。

首先定义一个变量,它是指向 char 的指针,通常只用作 单个字符串 。它初始化指向字符串文字 "what" 的指针。编译器 应该 也会抱怨列表中的初始化程序太多。

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


有点不同,可以看到这样的东西:

第一种情况:

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

第二次你有

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

另请注意,对于第一个版本,使用指向单个字符串的指针,表达式 str[i] = "newstring" 也应该导致警告,因为您尝试将指针分配给 单个char元素str[i].

该赋值在第二个版本中也是无效的,但还有另一个原因:str[i] 是一个 数组(包含五个 char 元素)并且你不能分配给一个数组,只能复制到它。所以你可以 尝试 strcpy(str[i], "newstring") 并且编译器不会抱怨。不过,这是 错误的 ,因为您试图将 10 个字符(记住终止符)复制到 5 个字符的数组中,这将越界导致 未定义的行为.

首先: 一个建议:请阅读arrays are not pointers and vice-versa!!

也就是说,为了启发这个特定场景,

  • 第一种情况,

    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

      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
    

    在句法上是无效的,因为您在 "pointer to complete object type" 以外的对象上使用数组下标 [] 运算符,例如

    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 behavior内存中属于字符串文字的元素之一

     str[i][j]='j';   //still invalid, as above.
    
  • 在第一个声明中

    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. [...]

    那是说标量类型可以用大括号括起来的初始值设定项,但只有一个表达式,但是在

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

    编译器将如何处理它。请注意,其余初始化器发生的情况是 bug。确认编译器应该给出诊断信息。

    [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"; 工作
    char *str[] = {"what","is","this"};
    

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

  • 片段

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

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

内存布局不同:

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 是位于堆栈上的连续二维字符数组。初始化数组时将字符串复制到此内存区域,并用零字节填充各个字符串以使数组具有规则形状。

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

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

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

char *ptr = "somestring";

这里"somestring"是一个字符串字面值,存储在内存的只读数据段ptr 是一个指针(就像同一段代码中的其他变量一样分配),它指向分配内存的第一个字节。

因此考虑这两个陈述

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

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

另一方面,如果我们写:

char ptr[] = "somestring";

这里的ptr其实不是一个指针,而是一个数组的名字(不像指针,它不占用额外的space内存)。它分配的字节数与 "somestring" 所需的字节数相同(不是只读),仅此而已。

因此考虑相同的两个语句和一个额外的语句

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

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

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


现在在这段代码中,

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

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

但在这段代码中

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

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

同上。

要消除混淆,您必须对指针、数组和初始值设定项有正确的理解。 C 编程初学者的一个常见误解是数组等同于指针。

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

char arr[10];

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

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

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

数组不可赋值,因此初始化列表的使用仅在数组声明时有效。

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

char array1[10];
char array2[10] = {'a','b','c','d','e','f','g','h','i','[=13=]'};
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] = '[=14=]';

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

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

char 数组可以通过常量 null 终止的字符串文字来初始化 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','[=21=]'};

这是一个约束违规,但您的编译器可能只是将 '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 ].

回想一下,字符串常量以 null 结尾 char arrays,因此以下表达式也是有效的:

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"; 相同。这绝不是C语言能保证的。

  • str[i][j] 尝试两次间接指针,即使它只有一个间接级别,因此您会遇到编译器错误。它与键入

    一样毫无意义

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

  • char* str = ...char str[] = ...的区别见FAQ: What is the difference between char s[] and char *s?

  • 关于char str[][5]={"what","is","this"};的情况,它创建了一个数组数组(二维数组)。最内层维度设置为 5,最外层维度由编译器自动设置,具体取决于程序员提供的初始值设定项数量。本例中为 3,所以代码等同于 char[3][5].

  • str[i] 为您提供数组数组中的数组编号 i。你不能在 C 中分配给数组,因为这就是语言的设计方式。此外,无论如何对字符串这样做都是不正确的,FAQ: How to correctly assign a new string value?


1) 这违反了 C11 6.7.9/2 的约束。另见 6.7.9/11。

案例一:

写的时候

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 Standards#6.7.9p11:

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

此外,为标量提供多个初始化器是undefined behavior
来自 C Standards#J.2 Undefined behavior:

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

因为根据标准它是未定义的行为,所以没有必要进一步讨论它。讨论 Part I.IIPart I.III 假设 - char *str="somestring",只是为了更好地理解char * 类型。
您似乎想要创建一个指向字符串的指针数组。在讨论了这两种情况之后,我在下面 post 添加了一个关于字符串指针数组的简要说明。

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

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

指向的对象后 i 处的字符

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

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

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

案例二:

写的时候

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

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

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

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

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

根据初始化列表,初始化二维数组的各个元素,其余元素设置为0

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

是的,这是无效的。
str[i]是一维数组。
根据 C 标准,数组不是可修改的左值。
来自 C Standards#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.

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

来自C Standards#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.

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

第一部分I.I第二部分
>> whereas str[i][j]='J'; is valid.

是的,只要 ij 是给定数组 str 的有效值即可。

str[i][j]char 类型,所以你可以给它分配一个字符。 当心,C 不检查数组边界并且越界访问数组是未定义的行为,其中包括 - 它可能偶然地完全按照程序员的意图或分段错误或静默生成不正确的结果或任何可能发生的事情。


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

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

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

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

"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|[=17=]|
        |    |    +-+-+-+-+-+-+-+-+-+--+
        +----+

但是你不应该这样做:

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

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

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

来自 C standard#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.


补充:

C语言中没有原生的字符串类型。在C语言中,一个字符串就是一个null-terminated array个字符。你应该知道数组和指针的区别吧

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

  1. 数组初始化,检查this
  2. 指针与数组等价,检查this and this.

案例 1 :

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

首先以上声明是无效,请正确阅读警告。 str是单指针,一次只能指向single个字符数组,不能指向multiple个字符数组。

bounty.c:3:2:警告:标量初始化器中的元素过多[默认启用]

str 是一个 char 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" 不会导致修改 code/read-only 部分,在这里您只是将 new address 分配给 str 这就是为什么有效但

*str='j'str[0][0]='j' 无效 因为你在这里修改 只读部分 ,尝试更改 str.

的首字母

案例二:

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

这里 str2D 数组,即 strstr[0],str[1],str[2] 本身存储在 RAMstack section 中,这意味着您可以更改每个 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.

希望对您有所帮助。