我最近开始使用 C 语言进行编程,来自 Java 和 Python。现在,在我的书中,我注意到要制作一个“Hello World”程序,语法是这样的:
char message[10]
strcpy(message, "Hello, world!")
printf("%s\n", message);
现在,这个例子使用了一个 char 数组,我想知道 - 字符串发生了什么?为什么我不能简单地使用其中之一?也许有不同的方法可以做到这一点?
C 没有也从来没有原生字符串类型。按照惯例,该语言使用char
以空字符终止的数组,即带有'\0'
. 该语言的标准库中的函数和宏提供了对以空字符结尾的字符数组的支持,例如,strlen迭代一个数组,char
直到它遇到一个'\0'
字符,而strcpy从源字符串复制,直到它遇到一个'\0'
.
在 C 中使用以 null 结尾的字符串反映了这样一个事实,即 C 只比汇编语言更高级一点。当时PDP-10 和 PDP-11 的汇编语言已经直接支持零终止字符串。
值得注意的是,C 字符串的这种特性会导致很多令人讨厌的缓冲区溢出错误,包括严重的安全漏洞。例如,如果您忘记对作为源参数传递给 的字符串进行空终止strcpy
,则该函数将继续从源字符串末尾之后的内存中复制顺序字节,直到它碰巧遇到一个0
,可能会覆盖任何有价值的信息都跟随着目标字符串在内存中的位置。
在您的代码示例中,字符串文字“Hello, world!” 将被编译成一个 14 字节长的数组char
。前 13 个字节将包含字母、逗号、空格和感叹号,最后一个字节将包含'\0'
由编译器自动添加的空终止符。如果您要访问数组的最后一个元素,您会发现它等于0
. 例如:
const char foo[] = "Hello, world!";
assert(foo[12] == '!');
assert(foo[13] == '\0');
但是,在您的示例中,message
只有 10 个字节长。strcpy
将把包括空终止符在内的所有 14 个字节写入内存,从message
. 前 10 个字节将被写入堆栈上分配的内存message
,其余 4 个字节将简单地写入堆栈的末尾。在这种情况下,将这四个额外字节写入堆栈的后果很难预测(在这个简单的示例中,它可能不会造成任何伤害),但在实际代码中,它通常会导致数据损坏或内存访问违规错误。
中没有string
类型C
。您必须使用 char 数组。
顺便说一句,您的代码将不起作用,因为数组的大小应该允许整个数组适合加上一个额外的零终止字符。
用你提到的语言注意它:
爪哇:
String str = new String("Hello");
Python:
str = "Hello"
Java和Python都有“字符串”的概念,C没有“字符串”的概念。C 具有可以“只读”或可操作的字符数组。
C:
char * str = "Hello"; // the string "Hello\0" is pointed to by the character pointer
// str. This "string" can not be modified (read only)
或者
char str[] = "Hello"; // the characters: 'H''e''l''l''o''\0' have been copied to the
// array str. You can change them via: str[x] = 't'
字符数组是一系列连续字符,末尾有一个唯一的标记字符(通常是 NULL 终止符'\0'
)。请注意,在上述情况下,哨兵字符会自动为您附加。
在 C 中,字符串只是一个字符数组,以空字节结尾。因此char*
,当您阅读 C 代码时,a 通常发音为“字符串”。
C 不支持一流的字符串类型。
C++ 有 std::string
C 没有像 Java 那样自己的 String 数据类型。
只有我们可以使用字符数组或字符指针在 C 中声明 String 数据类型 例如:
char message[10];
or
char *message;
但是您至少需要声明:
char message[14];
复制“你好,世界!” 进入消息变量。
首先,你不需要做所有这些。特别是,这strcpy
是多余的 - 您不需要将字符串复制到printf
它。您message
可以使用该字符串来定义。
其次,您没有为“Hello, World!”留出足够的空间。字符串(message
需要至少 14 个字符,允许空终止符多出一个)。
至于为什么,这是历史。在汇编程序中,没有字符串,只有字节、单词等。Pascal 有字符串,但静态类型存在问题,因此 -string[20]
与string[40]
. 即使在早期,也有一些语言可以避免这个问题,但这会导致间接和动态分配开销,这在当时更多是一个效率问题。
C 只是选择避免开销并保持非常低的水平。字符串是字符数组。数组与指向第一项的指针密切相关。当数组类型“衰减”为指针类型时,缓冲区大小信息会从静态类型中丢失,因此您不会遇到旧的 Pascal 字符串问题。
在 C++ 中,有一个std::string
类可以避免很多这些问题 - 并且具有动态分配开销,但现在我们通常不关心这一点。无论如何,std::string
它是一个库类——下面有 C 风格的字符数组处理。