【发布时间】:2010-11-03 00:18:00
【问题描述】:
我见过人们的代码为:
char *str = NULL;
我也看到了,
char *str;
我想知道,初始化字符串的正确方法是什么?你什么时候应该初始化一个带和不带 NULL 的字符串?
【问题讨论】:
-
您没有在所示代码中初始化字符串。您正在初始化一个指向字符的指针。
-
@San Jacinto:感谢您指出这一点!
我见过人们的代码为:
char *str = NULL;
我也看到了,
char *str;
我想知道,初始化字符串的正确方法是什么?你什么时候应该初始化一个带和不带 NULL 的字符串?
【问题讨论】:
你应该在使用它之前设置它。这是您必须遵守以避免未定义行为的唯一规则。无论您是在创建时初始化它还是在使用它之前分配给它都无关紧要。
就我个人而言,我更喜欢自己永远不要将变量设置为未知值,所以我通常会做第一个,除非它设置得非常接近(几行之内)。
事实上,在 C99 中,您不必再在块的顶部声明局部变量,我通常会推迟创建它,直到需要它,此时它也可以被初始化。
请注意,在某些情况下,变量会被赋予默认值(例如,如果它们是静态存储持续时间,例如在文件级别声明,在任何函数之外)。
局部变量没有这个保证。因此,如果您上面的第二个声明 (char *str;) 在函数内部,则其中可能包含垃圾,并且尝试使用它会调用上述可怕的未定义行为。
C99标准相关部分6.7.8/10:
如果具有自动存储持续时间的对象未显式初始化,则其值是不确定的。如果具有静态存储持续时间的对象未显式初始化,则:
- 如果有指针类型,则初始化为空指针;
- 如果它具有算术类型,则将其初始化为(正或无符号)零;
- 如果是聚合,则每个成员都根据这些规则进行初始化(递归);
- 如果是联合,则根据这些规则(递归)初始化第一个命名成员。
【讨论】:
我想知道,初始化字符串的正确方法是什么?
好吧,既然第二个 sn-p 定义了一个 未初始化 指向字符串的指针,我会说第一个。 :)
一般来说,如果你想安全起见,最好将所有指针初始化为NULL;这样,很容易发现源自未初始化指针的问题,因为取消引用 NULL 指针会导致崩溃(实际上,就标准而言,这是未定义的行为,但在我见过的每台机器上都是崩溃)。
但是,您不应将指向字符串的NULL 指针与空字符串混淆:指向字符串的NULL 指针意味着该指针不指向任何内容,而空字符串是“真实”的零长度字符串(即它只包含一个NUL 字符)。
char * str=NULL; /* NULL pointer to string - there's no string, just a pointer */
const char * str2 = ""; /* Pointer to a constant empty string */
char str3[] = "random text to reach 15 characters ;)"; /* String allocated (presumably on the stack) that contains some text */
*str3 = 0; /* str3 is emptied by putting a NUL in first position */
【讨论】:
str2(指向字符串字面量)没有被修改,str3 分配在堆栈上(或其他地方,取决于代码的放置位置),文字仅用于初始化。
str3 是一个数组,而不是一个指针!文字用于初始化,它在缓冲区 (str3) 中复制,编译器会自动调整大小以适应该字符串。看例如这里:msdn.microsoft.com/en-us/library/7w7xccx8%28VS.80%29.aspx.
str3 的类型为char[],一旦使用字符串文字初始化为char[38],它就变得完整。这种类型是一个可修改的左值。您不是在修改字符串文字,而是在修改与字符串文字具有相同类型和内容的左值。
这是一个关于 c 变量的一般问题,而不仅仅是 char ptrs。
在声明点初始化变量被认为是最佳实践。即
char *str = NULL;
是一件好事。这样你就永远不会有未知值的变量。例如,如果您稍后在您的代码中这样做
if(str != NULL)
doBar(str);
会发生什么。 str 处于未知(几乎可以肯定不是 NULL)状态
请注意,静态变量将为您初始化为零/NULL。如果您问的是本地人还是静态数据,从问题中不清楚
【讨论】:
编译器用默认值初始化全局变量,但必须初始化局部变量。
【讨论】:
一个未初始化的指针应该被认为是未定义的,因此为了避免使用未定义的值产生错误,最好使用它
char *str = NULL;
也是因为
char *str;
这将只是一个未分配的指针,指向某个地方,如果您忘记分配它,在使用时主要会导致问题,您将需要以任何方式分配它(或复制另一个指针)。
这意味着你可以选择:
NULL(这是一种经验法则)【讨论】:
这完全取决于您将如何使用它。在下文中,更有意义的是 not 初始化变量:
int count;
while ((count = function()) > 0)
{
}
【讨论】:
不要在声明“以防万一”时将所有指针变量初始化为 NULL。
如果您尝试使用尚未初始化的指针变量,编译器会警告您,除非您通过地址将其传递给函数(并且您通常这样做为了给它一个值)。
初始化指向 NULL 的指针与将其初始化为 可理解的 值不同,将其初始化为 NULL 只会禁用编译器告诉您没有的能力em> 将其初始化为合理的值。
如果您没有收到编译器警告,则仅在声明时初始化指向 NULL 的指针,或者您通过地址将它们传递给期望它们为 NULL 的函数。
如果在同一个屏幕上看不到指针变量的声明和第一次给它赋值的点,那么你的函数太大了。
【讨论】:
static const char str[] = "str";
或
static char str[] = "str";
【讨论】:
因为如果你传递一个 NULL 值 free() 不会做任何事情,你可以像这样简化你的程序:
char *str = NULL;
if ( somethingorother() )
{
str = malloc ( 100 );
if ( NULL == str )
goto error;
}
...
error:
cleanup();
free ( str );
如果由于某种原因 someorother() 返回 0,如果你还没有初始化 str 你会 在任何可能导致失败的地方释放一些随机地址。
对于 goto 的使用,我深表歉意,我知道有些人觉得它令人反感。 :)
【讨论】:
你的第一个 sn-p 是一个带初始化的变量定义;第二个sn-p是没有初始化的变量定义。
初始化字符串的正确方法是在定义它时提供一个初始化器。将其初始化为 NULL 或其他内容取决于您要使用它做什么。
还要注意你所说的“字符串”。 C 没有这种类型:通常C 上下文中的“字符串”表示“[一些] 字符的数组”。您在上面的 sn-ps 中有指向 char 的指针。
假设您有一个程序需要 argv[1] 中的用户名并将其复制到字符串“name”。当您定义 name 变量时,您可以保持它未初始化,或将其初始化为 NULL(如果它是指向 char 的指针),或使用默认名称进行初始化。
int main(int argc, char **argv) {
char name_uninit[100];
char *name_ptr = NULL;
char name_default[100] = "anonymous";
if (argc > 1) {
strcpy(name_uninit, argv[1]); /* beware buffer overflow */
name_ptr = argv[1];
strcpy(name_default, argv[1]); /* beware buffer overflow */
}
/* ... */
/* name_uninit may be unusable (and untestable) if there were no command line parameters */
/* name_ptr may be NULL, but you can test for NULL */
/* name_default is a definite name */
}
【讨论】:
你的意思是没有错误?好吧,这取决于情况。但我可以推荐一些经验法则。
首先,请注意,C 中的字符串与其他语言中的字符串不同。
它们是指向字符块的指针。其结尾以 0 字节或 NULL 终止符终止。因此以 null 结尾的字符串。
例如,如果您要执行以下操作:
char* str;
gets(str);
或以任何方式与 str 交互,那么这是一个巨大的错误。原因是因为正如我刚才所说,C 中的字符串不像其他语言那样是字符串。它们只是指针。 char* str 是指针的大小,并且永远都是。
因此,你需要做的是分配一些内存来保存一个字符串。
/* this allocates 100 characters for a string
(including the null), remember to free it with free() */
char* str = (char*)malloc(100);
str[0] = 0;
/* so does this, automatically freed when it goes out of scope */
char str[100] = "";
但是,有时您只需要一个指针。
例如
/* This declares the string (not intialized) */
char* str;
/* use the string from earlier and assign the allocated/copied
buffer to our variable */
str = strdup(other_string);
一般来说,这实际上取决于您希望如何使用字符串指针。 如果您只想在该函数的范围内使用它并且字符串相对较小,我的建议是使用固定大小的数组形式。或者将其初始化为 NULL。然后,您可以显式测试 NULL 字符串,这在传递给函数时很有用。
请注意,如果您使用的函数仅检查 NULL 以了解字符串的结尾在哪里,那么使用数组形式也可能会出现问题。例如strcpy 或 strcat 函数不关心你的缓冲区有多大。因此考虑使用像 BSD 的 strlcpy & strlcat 这样的替代方案。或者 strcpy_s & strcat_s (windows)。
许多函数也希望您传递正确的地址。所以再一次,请注意
char* str = NULL;
strcmp(str, "Hello World");
会因为 strcmp 不喜欢传入 NULL 而崩溃。
您已将此标记为 C,但如果有人使用 C++ 并阅读此问题,则尽可能切换到使用 std::string 并在需要与 API 交互的字符串上使用 .c_str() 成员函数这需要一个标准的以 null 结尾的 c 字符串。
【讨论】:
gets 是一个巨大的错误,无论你是否分配空间:-)
NULL,因为它引入了空指针常量的歧义,这是一个完全不同的概念.
gets_s 作为gets 的替代品,显然采用了一个额外的参数来指定缓冲区的大小。
fgets(...,stdin) 的接口层。他们应该刚刚弃用 gets 然后将其完全从 c2x 中删除 :-)