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

时间:2018-01-18 07:01:51

标签: c arrays pointers arrayofarrays

案例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 个答案:

答案 0 :(得分:26)

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

那就是说,为了启发这个特殊的场景,

  • 在第一种情况下

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

    不符合您的想法。这是一个约束违规,需要根据章节§6.7.9/ P2进行任何符合C实现的诊断:

      

    初始化程序不应尝试为未包含在实体中的对象提供值   被初始化。

    如果您启用了警告,则您(至少)会看到

      

    警告:标量初始值设定项中的多余元素

      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!!
    
  • 最后, considering you meant to write (as seen in comments)

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

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

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

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

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

答案 1 :(得分:17)

内存布局不同:

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字符串。

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

答案 2 :(得分:3)

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

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

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

对于第一种情况:

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

第二个你有

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

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

该分配在第二个版本中也是无效的,但出于另一个原因:str[i]是一个数组(五个str[i]个元素)并且您无法分配到一个数组,只复制到它。所以你可以尝试char并且编译器不会抱怨。它是错误的,因为你试图将10个字符(记住终结符)复制到一个包含5个字符的数组中,并且这将写出超出范围的未定义的行为。 / p>

答案 3 :(得分:2)

  • 在第一个声明中

    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):

      

    如果预处理转换单元或转换单元包含违反任何语法规则或约束的情况,则符合要求的实现应生成至少一条诊断消息(以实现定义的方式标识),即使该行为也明确指定为未定义或实施定义的

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

答案 4 :(得分:1)

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

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也有效,因为内存不是只读的。

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

现在在这段代码中,

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

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

但在此代码中

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

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

与上述相同。

答案 5 :(得分:0)

为了消除这种困惑,你必须正确理解指针,数组和初始化器。 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 *类型的变量,一个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 *的类型不兼容。< / p>

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

在你的例子中:

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

答案 6 :(得分:0)

  • 首先是

    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"};情况,它会创建一个数组数组(2D数组)。最内层维度设置为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.

答案 7 :(得分:0)

案例1:

写作时

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

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

第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

  

标量的初始值设定项应为单个表达式,可选择用大括号括起来。 ..

此外,为标量提供多个初始值设定项为undefined behavior 来自C Standards#J.2 Undefined behavior

  

标量的初始值设定项既不是单个表达式,也不是括在大括号中的单个表达式

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

第III部分
>> then str[i]="newstring"; is valid

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

"newstring"是一个字符串文字和一个字符串文字衰变成一个指针,除了用于初始化类型为char *的数组时,在这里你试图将它分配给{{1} }类型。因此,编译器将其报告为警告。

第III部分
char

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

案例2:

写作时

char

然后char str[][5]={"what","is","this"}; 无效,而str[i]="newstring";有效。

第二部分.I
str[i][j]='J';

这是绝对正确的。 这里,>> char str[][5]={"what","is","this"};是一个二维数组。根据初始化程序的数量,编译器将自动设置第一个维度。 在这种情况下,str的内存视图将是这样的:

str[][5]

基于初始化列表,将初始化2D数组的各个元素,并将其余元素设置为 str +-+-+-+-+-+ str[0] |w|h|a|t|0| +-+-+-+-+-+ str[1] |i|s|0|0|0| +-+-+-+-+-+ str[2] |t|h|i|s|0| +-+-+-+-+-+

第II.II部分
0

是的,这是无效的。
>> then str[i]="newstring"; is not valid是一维数组 根据C标准,数组不是可修改的左值 来自C Standards#6.3.2.1p1

  

左值是一个表达式(对象类型不是void)可能指定一个对象; 64)如果左值在评估时没有指定对象,则行为是未定义的。当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。可修改的左值是一个左值,它没有数组类型,没有不完整的类型,没有const限定类型,如果是结构或联合,则没有任何成员(包括递归地,任何成员)或具有常量类型的所有包含的聚合或联合的元素。

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

来自C Standards#6.3.2.1p3

  

除非它是sizeof运算符,_Alignof运算符或一元&amp;运算符的操作数。 operator,或者是一个用于初始化数组的字符串文字,一个类型为&#39;&#39;类型&#39;&#39;&#39;转换为类型为&#39;&#39;指向类型&#39;&#39;的指针。它指向数组对象的初始元素,而不是左值。

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

第II.III部分
char

是的,只要char *>> whereas str[i][j]='J'; is valid.是给定数组i的有效值,此选项就有效。

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

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

str[i][j]

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

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]为1,那么str[i]="newstring"; 指针现在指向字符串文字i

str[1]

但是你不应该这样做

"newstring"

(假设为 +----+ +-+-+-+-+-+-+-+-+-+--+ str[1]| |--->|n|e|w|s|t|r|i|n|g|\0| | | +-+-+-+-+-+-+-+-+-+--+ +----+ str[i][j]='j'; ,因此i=1是第二个字符串的第一个字符)

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

来自C standard#6.4.5p7

  

如果这些数组的元素具有适当的值,则未指定这些数组是否相同。如果程序试图修改这样的数组,则行为是未定义的。

附加:

C语言中没有本机字符串类型。在C语言中,字符串是null-terminated array个字符。你应该知道数组和指针之间的区别。

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

  1. 数组初始化,检查this
  2. 指针和数组的等效性,请检查thisthis

答案 8 :(得分:0)

案例1:

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

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

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

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

如你所说 str [i] =&#34; newstring&#34 ;;是有效的,而str [i] [j] =&#39; j&#39 ;;无效。

str= "new string"未导致修改代码/只读部分,此处您只是将new address分配给str这就是为什么&## 1}} 39;有效但

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

案例2:

str

此处char str[][5]={"what","is","this"}; str数组,2Dstr本身存储在str[0],str[1],str[2] stack section中,这意味着您可以更改RAM str[i] {1}}内容。

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

str[i]= "new string";这是不可能的,因为str[0]本身就是一个数组而数组是常量指针(不能改变地址),你可以&# 39; t分配新地址。

只需第一个案例 str="new string"valid因为strpointer,而非array第二种情况 str[0]="new string"not valid,因为strarray而不是pointer

我希望它有所帮助。