在 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
注意sizeof 和strlen 之间的区别 - 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 部分,用于静态、常量数据)。变量greeting 和greetingPtr 存储在更高的地址,对应于我系统上的堆栈。如您所见,greetingPtr 存储字符串文字"hello" 的地址,而greeting 存储字符串内容的副本。
这就是事情可能会让人感到困惑的地方。让我们看看下面的打印语句:
printf( "greeting: %s\n", greeting );
printf( "greetingPtr: %s\n", greetingPtr );
greeting 是 char 的 6 元素数组,greetingPtr 是指向 char 的指针,但我们以完全相同的方式将它们都传递给 printf,字符串为被正确打印出来;怎么会这样?
除非它是sizeof 或一元& 运算符的操作数,或者是用于在声明中初始化另一个数组的字符串字面量,否则为“N 元素数组”类型的表达式的T”将被转换(“衰减”)为“指向T的指针”类型的表达式,表达式的值将是数组第一个元素的地址。
在printf 调用中,表达式greeting 的类型为“char 的6 元素数组”;因为它不是sizeof 或一元& 运算符的操作数,所以它被转换(“衰减”)为“指向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" 也是一个数组表达式。在第一个声明中,由于它用于在声明中初始化另一个数组,因此复制了数组的内容。在第二个声明中,目标是指针,而不是数组,因此表达式从数组类型转换为指针类型,并将生成的指针值复制到变量中。