What is the difference between char*str={“foo”,…} and char str[][5]={“foo”,…} array definitions?
案例1:我写的时候
1 | char*str={"what","is","this"}; |
那么
案例2:我写的时候
1 | char str[][5]={"what","is","this"}; |
那么
为什么会这样? 我是一个初学者,在阅读其他答案后已经非常困惑。
首先:一个建议:请阅读有关数组不是指针,反之亦然!
也就是说,为了启发这种特殊情况,
-
在第一种情况下,
1char*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
1char*str={"what","is","this"};但是,打开严格一致性的(ny)编译器应该拒绝编译代码。如果编译器选择编译并生成二进制文件,那么行为就不会影响C语言的定义范围,这取决于编译器的实现(因此可以有很大的不同)。
在这种情况下,编译器决定此语句在功能上仅与
char*str="what"; 相同所以,这里
str 是一个指向char 的指针,它指向一个字符串文字。
您可以重新分配指针,1str="newstring"; //this is valid但是,像一个声明
1str[i]="newstring";将无效,因为在这里,尝试转换指针类型并将其存储到
char 类型中,其中类型不兼容。在这种情况下,编译器应该抛出有关无效转换的警告。此后,声明如
1str[i][j]='J'; // compiler error在语法上是无效的,因为你在不是"指向完整对象类型的指针"的东西上使用了Array subscripting
[] 运算符,就像1
2
3
4str[i][j] = ...
^^^------------------- cannot use this
^^^^^^ --------------------- str[i] is of type 'char',
not a pointer to be used as the operand for [] operator. -
另一方面,在第二种情况下,
str 是一个数组数组。你可以改变单个数组元素,1str[i][j]='J'; // change individual element, good to go.但你不能分配给一个数组。
1str[i]="newstring"; // nopes, array type is not an lvalue!!
-
最后,考虑你打算写(如评论中所示)
1char* str[ ] ={"what","is","this"};在第一种情况下,数组的逻辑相同。这使得
str 成为一个指针数组。所以,数组成员是可分配的,所以,1str[i]="newstring"; // just overwrites the previous pointer完全没问题。但是,作为数组成员存储的指针是指向字符串文字的指针,因此出于上述原因,当您要修改属于字符串文字的内存中的一个元素时,调用未定义的行为。
1str[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| +--------+ +-----+ |
在此内存布局中,
1 2 3 4 5 6 7 8 9 10 | char str[][5] = {"what","is","this"}; str +-----+ |what0| +-----+ |is000| +-----+ |this0| +-----+ |
在这种情况下,
这两种内存布局从根本上是不相容的。您不能将任何一个传递给期望指向另一个的函数。但是,访问单个字符串是兼容的。当你写
在第一种情况下,很明显这个指针只是从内存加载。在第二种情况下,指针是通过数组指针衰减创建的:
首先,您定义一个变量,该变量是指向
第二个定义使
稍微不同,可以看到这样的事情:
对于第一种情况:
1 2 3 | +-----+ +--------+ | str | --> |"what" | +-----+ +--------+ |
而你的第二个
1 2 3 | +--------+--------+--------+ |"what" |"is" |"this" | +--------+--------+--------+ |
另请注意,对于第一个版本,如果指向单个字符串,则表达式
该分配在第二个版本中也是无效的,但出于另一个原因:
-
在第一个声明中
1char *str={"what","is","this"};声明
str 指向char 的指针并且是标量。标准说6.7.9初始化(p11):
The initializer for a scalar shall be a single expression, optionally enclosed in braces. [...]
也就是说,标量类型可以使用括号封闭的初始化器,但只有一个表达式,但是如果有的话
1char *str = {"what","is","this"}; // three expressions in brace enclosed initializer编译器是如何处理它的。请注意,其余初始化程序会发生什么错误。确认编制者应给出诊断信息。
1[Warning] excess elements in scalar initializer5.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"; 工作1char *str[] = {"what","is","this"};在这种情况下,
str[i] 是char * 类型,并且可以为其分配字符串文字,但修改字符串文字str[i] 指向将调用未定义的行为。那说你不能做str[0][0] = 'W' 。 -
片段
1char str[][5]={"what","is","this"};将
str 声明为char 的数组数组。str[i] 实际上是一个数组,并且数组是不可修改的左值,因此您不能将它们用作赋值运算符的左操作数。这使str[i]="newstring"; 无效。虽然str[i][j]='j'; 有效,因为可以修改数组的元素。
仅仅因为你说其他答案令我困惑,让我们先看一个更简单的例子来看看发生了什么
1 | char *ptr ="somestring"; |
这里
因此cnosider这两个陈述
1 2 | char *ptr2 = ptr; //statement 1 OK ptr[1] = 'a'; //statement 2 error |
语句1执行完全有效的操作(将1指针指向另一个),但语句2不是有效操作(尝试写入只读位置)。
另一方面,如果我们写:
1 | char ptr[] ="somestring"; |
这里ptr实际上不是指针,而是数组的名称(与指针不同,它不占用内存中的额外空间)。它分配
因此,请考虑相同的两个陈述和一个额外的陈述
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"}; |
但是在这段代码中
1 | char str[][] = {"what","is","this"}; |
与上述相同的是。
情况1 :
1 | char*str={"what","is","this"}; |
首先上述声明无效,请正确阅读警告。
bounty.c:3:2:警告:标量初始值设定项中的多余元素[默认启用]
正如你所说str [i] ="newstring";是有效的,而str [i] [j] ='j';是无效的。
案例2:
1 | char str[][5]={"what","is","this"}; |
这里
简单地说,在第一种情况下,
我希望它有所帮助。
情况1:
当我写作
1 | char*str={"what","is","this"}; |
那么
第一部分.I
在此语句中,
编译时,您必须在此语句中收到警告消息:
1 2 3 | warning: excess elements in scalar initializer char*str={"what","is","this"}; ^ |
警告的原因是 - 您为标量提供了多个初始值设定项。
[算术类型和指针类型统称为标量类型。]
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部分的假设 -
似乎你想要创建一个指向字符串的指针数组。在谈到这两个案例之后,我在这篇文章的下面添加了一个关于字符串指针数组的简要说明。
第一部分.II
不,这是无效的。
同样,由于转换不兼容,编译器必须在此语句上发出警告消息。
由于
第III部分
是的,这是无效的。
案例2:
当我写作
1 | char str[][5]={"what","is","this"}; |
那么
第二部分.I
这绝对是正确的。
这里,
在这种情况下,
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阵列的各个元素,并将其余元素设置为
第II.II部分
是的,这是无效的。
根据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.
由于
第II.III部分
是的,只要
请注意,C不检查数组边界并且访问数组越界是未定义的行为,包括 - 它可能偶然地完成程序员的意图或分段错误或者无声地生成错误结果或任何事情都可能发生。
假设在案例1中,您想要创建一个指向字符串的指针数组。
它应该是这样的:
1 2 | char *str[]={"what","is","this"}; ^^ |
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| | | +-+-+-+-+--+ +----+ |
所以,这非常好:
1 | str[i]="newstring"; |
假设
1 2 3 4 | +----+ +-+-+-+-+-+-+-+-+-+--+ str[1]| |--->|n|e|w|s|t|r|i|n|g|\0| | | +-+-+-+-+-+-+-+-+-+--+ +----+ |
但你不应该这样做:
1 | str[i][j]='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语言中,字符串是以空字符结尾的字符数组。 你应该知道数组和指针之间的区别。
我建议你阅读以下内容,以便更好地理解数组,指针,数组初始化:
好。
-
首先
1char*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个元素,每个元素的类型为
初始化列表可用于以方便的方式初始化阵列。下面使用初始化列表的相应值初始化数组元素:
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'; |
1 | char array[10] ="abcdefghi"; |
但是以下内容无效:
1 2 | char array[10]; array ="abcdefghi"; // As mentioned before, arrays are not assignable |
现在,让我们来指点一下......
指针是可以存储另一个变量的地址的变量,通常是相同类型的变量。
请考虑以下声明:
1 | char *ptr; |
这声明了一个
与数组不同,指针是可分配的。因此以下内容有效:
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'}; |
这是一个约束违规,但您的编译器可能只是将
如果在运行时已取消引用此指针,则会导致访问无效内存的运行时错误,从而导致程序崩溃。
在你的例子中:
1 | char *str = {"what","is","this"}; |
再次,这是一个约束违规,但您的编译器可能会将字符串
warning: excess elements in scalar initializer .
现在,这是我们如何消除关于指针和数组的混淆:
在某些上下文中,数组可能会衰减为指向数组第一个元素的指针。因此以下内容有效:
1 2 | char arr[10]; char *ptr = arr; |
通过在赋值表达式中使用数组名称
1 | char *ptr = &arr[0]; |
请记住,
回想一下,字符串文字是常量的空终止
1 | char *ptr ="abcdefghi"; // the array"abcdefghi" decays to a pointer to the first element 'a' |
现在,在您的情况下,
由于数组不可分配,