【问题标题】:What is the difference between char*str={"foo",...} and char str[][5]={"foo",...} array definitions?char*str={"foo",...} 和 char str[][5]={"foo",...} 数组定义有什么区别?
【发布时间】:2018-06-27 04:26:15
【问题描述】:

案例一:当我写作时

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'; 有效。

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

【问题讨论】:

  • 也许你的意思是char * str[] = {"what", "is", "this"}; 第一个?
  • @ajay brahmakshatriya 是的
  • @hacks 最后一次编辑是由 OP 本人完成的。我同意它使某些答案无效,但需要解决正确的问题。
  • @ajay 如果他的编辑使答案无效,则 OP 可能会提出一个新问题。
  • 原始问题(使用char *str 而不是char *str[])甚至没有意义,因为OP 说“然后str[i]="newstring"; 有效,而str[i][j]='j'; 无效。”,其中应用于char * 意味着将指针值分配给只读字符串文字内存中的 char 元素是“有效的”。什么是“有效”和“无效”的描述更适合char *str[],这是一个更有意义的问题。正如 OP 所证实的,这显然只是一个错字。

标签: c arrays pointers arrayofarrays


【解决方案1】:

首先:建议:请阅读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
    

    在语法上无效,因为您在不是“指向完整对象类型的指针”的东西上使用数组下标[] 运算符,例如

    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
    

    完全没问题。但是,存储为数组成员的指针是指向字符串字面量的指针,因此出于上述相同的原因,当您想要修改属于字符串字面量的内存

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

【讨论】:

  • 你答案的第一部分是不对的。如果str 的类型为char *,则str[i]char 左值,而不是指针。如果你给它分配一个字符串文字,你至少会在编译时得到一个警告assignment make integer from pointer without a cast。然后在尝试写入常量内存时出现运行时分段错误。
  • 事实上char*str={"what","is","this"}; 是违反约束的,需要任何符合C 实现的诊断。 N1570 6.7.9p2:“任何初始化程序都不应尝试为未包含在正在初始化的实体中的对象提供值。” (恕我直言,不幸的是 gcc 默认将此视为非致命警告。)
【解决方案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 是位于堆栈上的连续二维字符数组。数组初始化时,字符串被复制到这个内存区域,各个字符串用零字节填充,以使数组具有规则的形状。

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

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

【讨论】:

    【解决方案3】:

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

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


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

    对于第一种情况:

    +-----+ +--------+ |字符串 | --> | “什么” | +-----+ +--------+

    第二次你有

    +--------+--------+--------+ | “什么” | “是” | “这个” | +--------+--------+--------+

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

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

    【讨论】:

    • 指向char 的指针不是“只是一个字符串”。它可能是指向字符串的指针,但根据定义,字符串是字符序列,而不是指针。
    【解决方案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):

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

    • 您声称“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'

    • sn-p

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

      str 声明为chars 的数组。 str[i] 实际上是一个数组,因为数组是不可修改的左值,所以你不能将它们用作赋值运算符的左操作数。这使str[i]="newstring"; 无效。而str[i][j]='J'; 之所以有效,是因为可以修改数组的元素。

    【讨论】:

      【解决方案5】:

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

      char *ptr = "somestring";
      

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

      因此考虑这两个陈述

      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 正在执行完全有效的操作(将数组名分配给指针,数组名返回第一个字节的地址),语句 2 也有效,因为内存不是只读的。

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


      现在在这段代码中,

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

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

      但在这段代码中

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

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

      以下同上。

      【讨论】:

        【解决方案6】:
        • 开始

          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。

        【讨论】:

          【解决方案7】:

          要消除混淆,您必须正确理解指针、数组和初始化程序。 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 数组可以通过字符串字面量进行初始化,这些字面量是常量以 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','\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&amp;arr[0]是它的地址,类型是char *,与变量ptr兼容。

          回想一下,字符串文字是常量以 null 结尾的 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] 是一个数组元素,它本身不是一个数组,并且是可赋值的。

          【讨论】:

          • 您的示例中的char *ptr = 无法编译的原因是您使用了错误的类型。问题中的char*str={"what","is","this"}; 无法编译的原因是它的初始化程序数量错误。这是完全不同的代码。
          【解决方案8】:

          案例一:

          当我写作时

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

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

          第一部分
          &gt;&gt; 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

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

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

          第一部分第二部分
          &gt;&gt; then str[i]="newstring"; is valid

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

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

          第一部分第三部分
          &gt;&gt; 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'; 有效。

          第二部分.I
          &gt;&gt; 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

          第二部分.II
          &gt;&gt; then str[i]="newstring"; is not valid

          是的,这是无效的。
          str[i] 是一维数组。
          根据 C 标准,数组不是可修改的左值。
          来自C Standards#6.3.2.1p1

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

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

          来自C Standards#6.3.2.1p3

          除非它是 sizeof 运算符、_Alignof 运算符或一元 & 运算符的操作数,或者是用于初始化数组的字符串文字,否则类型为 ''array of type'' 的表达式将转换为类型为“类型指针”的表达式,它指向数组对象的初始元素,并且不是左值。

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

          第二部分第三部分
          &gt;&gt; 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|\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=1j=0,所以str[i][j] 是第二个字符串的第一个字符)

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

          来自C standard#6.4.5p7

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


          补充:

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

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

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

          【讨论】:

            【解决方案9】:

            案例 1:

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

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

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

            strchar pointer,它存储在 RAM 的 section 部分,但它的 contents 存储在 RAM 的 code(Can't modify the content 部分,因为 str 是用 string(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 的第一个字母。

            案例2:

            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] 本身是一个数组,而数组是常量指针(不能改变地址),你不能分配新地址。

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

            希望对你有帮助。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-04-21
              • 2010-12-12
              • 2014-07-28
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2015-03-08
              相关资源
              最近更新 更多