【问题标题】:Understanding two ways of declaring a C string [duplicate]了解声明 C 字符串的两种方法 [重复]
【发布时间】:2015-02-23 00:19:21
【问题描述】:

几周前,我开始学习编程语言 C。我对 HMTL/CSS、Javscript、PHP 和基本服务器管理等 Web 技术有一定的了解,但 C 让我很困惑。据我了解,C 语言没有字符串的数据类型,只有字符,但我可能错了。

我听说有两种声明字符串的方法。这两行声明字符串有什么区别:

a.) char stringName[];
b.) char *stringName;

我知道char stringName[]; 是一个字符数组。但是,第二行让我感到困惑。据我了解,第二行是一个指针变量。指针变量不应该是另一个变量的内存地址吗?

【问题讨论】:

  • 在 c 中,stirng 是 char 到并包括 '\0' 的数组。它不是一个类型。
  • 是的,我得到了那部分,但我仍然没有得到 b 部分。当它是一个指针时,它是一个字符串。
  • 这个问题已经在这个链接stackoverflow.com/questions/10186765/…回答了,看看吧。
  • 字符串文字是应用程序只读内存中的静态char[] 数组。 char s[] = "hello"; 为 6 个字符(5 + 空终止符)的本地数组分配内存,并将源字符复制到该内存中。而char *s = "hello"; 只是指向原始静态数组所在的内存地址。

标签: c string types char declaration


【解决方案1】:

如您所说,在 C 语言中,“字符串”是char 的数组。 C 规范中内置的大多数字符串函数都期望字符串为“NUL 终止”,这意味着字符串的最后一个char0。不是表示数字零的代码,而是0 的实际值。

例如,如果你的平台使用 ASCII,那么下面的“字符串”就是“ABC”:

char myString[4] = {65, 66, 67, 0};

当您使用char varName[] = "foo" 语法时,您是在堆栈上分配字符串(或者如果它在全局空间中,您是在全局分配它,但不是动态分配。)

与您可能使用过的许多其他语言相比,C 中的内存管理更加手动。特别是,有一个“指针”的概念。

char *myString = "ABC"; /* Points to a string somewhere in memory, the compiler puts somewhere. */

现在,char * 是“指向 char 或 char 数组的地址”。请注意该语句中的“或”,对于程序员来说,了解情况是什么很重要。

确保您执行的任何字符串操作不超过分配给指针的内存量也很重要。

char myString[5];
strcpy(myString, "12345"); /* copy "12345" into myString. 
                            * On no! I've forgot space for my nul terminator and 
                            * have overwritten some memory I don't own. */

“12345”实际上是 6 个字符长(不要忘记末尾的 0),但我只保留了 5 个字符。这就是所谓的“缓冲区溢出”,是许多严重错误的原因。

“[]”和“*”之间的另一个区别是创建一个数组(如您所料)。另一个不保留任何空间(除了用于保存指针本身的空间。)这意味着在您将其指向您知道有效的地方之前,不应使用指针的值来读取或写入。

另一点(评论中有人提出)

您不能将数组作为参数传递给 C 中的函数。当您尝试时,它会自动转换为指针。这就是为什么我们传递指向字符串的指针而不是字符串本身

【讨论】:

  • 还有一件更重要的事情你需要理解,但这个答案没有涵盖,那就是你不能将数组作为参数传递给 C 中的函数。当你尝试时,它自动转换为指针。这就是为什么我们传递指向字符串的指针而不是字符串本身。
  • 你确定这句话是对的吗? ""12345" 实际上是 6 个字符长,但我只保留了 5 个字符"。没有“myString[5];”保留 6 个字符?
  • @Roh 不,它是一个常规数组,就像 int arr[10] 一样,它有 10 个元素和索引 0 到 9(含)。
【解决方案2】:

在 C 中,string 是一系列字符值,后跟一个 0 值字节1。所有处理字符串的库函数都使用 0 终止符来标识字符串的结尾。字符串存储char 的数组,但并非所有char 的数组都包含字符串。

例如,字符串"hello" 表示为字符序列{'h', 'e', 'l', 'l', 'o', 0}2 要存储该字符串,您需要一个由char 组成的 6 元素数组 - 5 个字符加上 0终结者:

char greeting[6] = "hello";

char greeting[] = "hello";

在第二种情况下,数组的大小是根据用于初始化它的字符串的大小计算的(计算 0 终止符)。在这两种情况下,您都将创建一个由 char 组成的 6 元素数组,并将字符串文字的内容复制到其中。除非数组是在文件范围内声明的(排除任何函数)或使用 static 关键字,否则它仅在声明的块的持续时间内存在。

字符串 literal "hello" 也存储在 char 的 6 元素数组中,但它的存储方式是在程序加载到内存并保存时分配直到程序终止3,并且在整个程序中可见。当你写

char *greeting = "hello";

您正在将包含字符串文字的数组的第一个元素的地址分配给指针变量greeting

一如既往,一张图片胜过一千个字。这是一个简单的小程序:

#include <string.h>
#include <stdio.h>
#include <ctype.h>

int main( void )
{
  char greeting[]   = "hello";  // greeting contains a *copy* of the string "hello";
                                // size is taken from the length of the string plus the
                                // 0 terminator

  char *greetingPtr = "hello";  // greetingPtr contains the *address* of the 
                                // string literal "hello"

  printf( "size of greeting array:              %zu\n", sizeof greeting );
  printf( "length of greeting string:           %zu\n", strlen( greeting ) );
  printf( "size of greetingPtr variable:        %zu\n", sizeof greetingPtr );

  printf( "address of string literal \"hello\":   %p\n", (void * ) "hello" );
  printf( "address of greeting array:           %p\n", (void * ) greeting );
  printf( "address of greetingPtr:              %p\n", (void * ) &greetingPtr );
  printf( "content of greetingPtr:              %p\n", (void * ) greetingPtr );

  printf( "greeting:                            %s\n", greeting );
  printf( "greetingPtr:                         %s\n", greetingPtr );

  return 0;
}

这是输出:

size of greeting array:              6
length of greeting string:           5
size of greetingPtr variable:        8
address of string literal "hello":   0x4007f8
address of greeting array:           0x7fff59079cf0
address of greetingPtr:              0x7fff59079ce8
content of greetingPtr:              0x4007f8
greeting:                            hello
greetingPtr:                         hello

注意sizeofstrlen 之间的区别 - strlen 将所有字符计数到(但不包括)0 终止符。

以下是内存中的内容:

Item            Address            0x00 0x01 0x02 0x03
----            -------            ---- ---- ---- ----
"hello"         0x4007f8            'h'  'e'  'l'  'l'
                0x4007fc            'o' 0x00  ???  ???
                ...
greetingPtr     0x7fff59079ce8     0x00 0x00 0x00 0x00
                0x7fff59879cec     0x00 0x40 0x7f 0xf8
greeting        0x7fff59079cf0      'h'  'e'  'l'  'l'
                0x7fff59079cf4      'o' 0x00  ???  ???

字符串文字"hello" 存储在一个不同的低地址(在我的系统上,这对应于可执行文件的.rodata 部分,用于静态、常量数据)。变量greetinggreetingPtr 存储在更高的地址,对应于我系统上的堆栈。如您所见,greetingPtr 存储字符串文字"hello"地址,而greeting 存储字符串内容的副本。

这就是事情可能会让人感到困惑的地方。让我们看看下面的打印语句:

printf( "greeting:                            %s\n", greeting );
printf( "greetingPtr:                         %s\n", greetingPtr );

greetingchar 的 6 元素数组,greetingPtr 是指向 char 的指针,但我们以完全相同的方式将它们都传递给 printf,字符串为被正确打印出来;怎么会这样?

除非它是sizeof 或一元&amp; 运算符的操作数,或者是用于在声明中初始化另一个数组的字符串字面量,否则为“N 元素数组”类型的表达式T”将被转换(“衰减”)为“指向T的指针”类型的表达式,表达式的值将是数组第一个元素的地址。

printf 调用中,表达式greeting 的类型为“char 的6 元素数组”;因为它不是sizeof 或一元&amp; 运算符的操作数,所以它被转换(“衰减”)为“指向char 的指针”(char *)类型的表达式,并且 第一个元素的地址实际上传递给printf。 IOW,它的行为完全类似于下一个 printf 调用中的 greetingPtr 表达式4

%s 转换说明符告诉printf 其对应参数的类型为char *,并且它应该打印出从该地址开始的字符值,直到它看到 0 终止符。

希望能有所帮助。


1.通常称为NUL终结者;这不应与 NULL 指针常量混淆,后者也是 0 值,但用于不同的上下文。

2. 您还将看到终止的 0 值字节写入为'\0'。前导反斜杠“转义”该值,因此它不会被视为 字符 '0' (ASCII 48),而是被视为 0 (ASCII 0 ))。

3.在实践中,在生成的二进制文件中为其预留空间,通常在标记为只读的部分中;试图修改字符串 literal 的内容会调用未定义的行为。

4. 这也是为什么greeting 的声明复制 字符串内容到数组,而greetingPtr 的声明复制字符串的第一个元素的地址。字符串文字"hello" 也是一个数组表达式。在第一个声明中,由于它用于在声明中初始化另一个数组,因此复制了数组的内容。在第二个声明中,目标是指针,而不是数组,因此表达式从数组类型转换为指针类型,并将生成的指针值复制到变量中。

【讨论】:

  • 建议将ASCII 术语称为NUL,而不是NUL,如各种C 规范中指定的“空字符”。不错的答案
【解决方案3】:

在 C(和 C++)中,数组和指针的表示方式类似;数组由数组中第一个元素的地址表示(这足以访问其他元素,因为元素在数组中的内存中是连续的)。这也意味着数组本身并不指示它的结束位置,因此您需要某种方式来标识数组的结尾,或者通过将长度作为单独的变量传递,或者使用一些约定(例如有一个哨兵值放置在数组的最后一个位置,表示数组的结束)。对于字符串,后者是通用约定,'\0'(NUL 字符)表示字符串的结尾。

【讨论】:

  • 数组是一个数组,既不是指向其第一个元素的指针,也不是这样表示的。它肯定知道它有多大,只要问sizeof它占用了多少字节!有一个自动转换(数组衰减),但这是一个不同的问题。
  • 仅当您直接在固定大小的数组上调用sizeof() 时,而不是在指向数组的指针上。您必须注意固定长度数组与动态长度数组。
  • 不要将编译器语法与汇编中的表示相混淆。是的,如果您执行 'char foo[] = "Hello world!";' 之类的操作,编译器仍将保留该范围内的大小,但您最好相信引用 foo 的程序集只是一个地址,表示数组中第一个元素的地址。
  • 话虽如此,我的措辞非常谨慎......我故意写“相似”而不是“相同”来考虑这个确切的差异(并且可能的反应是我过于简化,甚至虽然过度简化实际上对新手有帮助)。
  • Lie-to-children 的基本前提是他们有必要将一个复杂的主题简化为孩子可以掌握的东西,但“足够真实”(稍后会建立并纠正)。麻烦的是,“足够真实”的部分在这里不成立,因为你假装不存在的复杂性是问题的核心。
猜你喜欢
  • 1970-01-01
  • 2014-01-03
  • 2011-05-06
  • 2021-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
相关资源
最近更新 更多