用字符串字面量来初始化char []是不好的做法吗?


44

在CodeGuru上读了一个标题为“ strlen vs sizeof”的线程其中一个答复指出“ char用字符串文字初始化数组仍然是[sic]不好的做法”。

这是真的吗?还是他的观点(尽管是“精英成员”)?


这是原始问题:

#include <stdio.h>
#include<string.h>
main()
{
    char string[] = "october";
    strcpy(string, "september");

    printf("the size of %s is %d and the length is %d\n\n", string, sizeof(string), strlen(string));
    return 0;
}

对。大小应为长度加1是?

这是输出

the size of september is 8 and the length is 9

大小一定是10。就像在计算strcpy改变字符串长度之前计算它的sizeof字符串一样。

我的语法有问题吗?


这是答复

无论如何,用字符串文字初始化char数组都是不好的做法。因此,请始终执行以下任一操作:

const char string1[] = "october";
char string2[20]; strcpy(string2, "september");

注意第一行上的“ const”。可能是作者假设使用c ++而不是c吗?在c ++中,这是“不好的做法”,因为文字应为const,并且任何最新的c ++编译器都会给出有关将const文字分配给非const数组的警告(或错误)。
安德烈(André)

@AndréC ++将字符串文字定义为const数组,因为这是处理它们的唯一安全方法。C 并不是问题所在,所以您有一条强制执行安全操作的社会规则
Caleth,

@Caleth。我知道,我更想证明答复的作者从c ++的角度出发正在接近“不良做法”。
安德烈(André)

@André在C ++中这不是一个坏习惯,因为它不是惯例,这是一个直截了当的类型错误。它应该是 C语言中的类型错误,但不是这样,因此您必须有一个样式指导规则来告诉您“这是被禁止的”
Caleth,

Answers:


59

无论如何,用字符串文字初始化char数组都是不好的做法。

该评论的作者从未真正对其进行过论证,我觉得这一说法令人困惑。

在C(你已经标记以此为C),那是相当多的唯一途径初始化数组char与字符串值(初始化为分配不同)。你可以写

char string[] = "october";

要么

char string[8] = "october";

要么

char string[MAX_MONTH_LENGTH] = "october";

在第一种情况下,数组的大小取自初始化程序的大小。字符串文字存储为char以0结尾的字节数组,因此数组的大小为8(“ o”,“ c”,“ t”,“ o”,“ b”,“ e”,“ r”, 0)。在后两种情况下,将数组的大小指定为声明的一部分(8和MAX_MONTH_LENGTH,无论发生什么情况)。

不能做的就是写类似

char string[];
string = "october";

要么

char string[8];
string = "october";

等。在第一种情况下,声明string不完全的,因为没有指定数组大小,而且也没有初始化从采取的大小。在这两种情况下,这=都不起作用,因为a)数组表达式(例如string可能不是赋值的目标)和b)=运算符未定义为将一个数组的内容复制到另一个数组。

同样,您不能写

char string[] = foo;

foo的另一个数组在哪里char?这种初始化形式仅适用于字符串文字。

编辑

我应该对此进行修改,说您也可以使用数组样式的初始化程序来初始化数组以容纳字符串

char string[] = {'o', 'c', 't', 'o', 'b', 'e', 'r', 0};

要么

char string[] = {111, 99, 116, 111, 98, 101, 114, 0}; // assumes ASCII

但是使用字符串字面量在眼中更容易。

编辑2

为了在声明之外分配数组的内容,您将需要使用strcpy/strncpy(对于0终止的字符串)或memcpy(对于任何其他类型的数组):

if (sizeof string > strlen("october"))
  strcpy(string, "october");

要么

strncpy(string, "october", sizeof string); // only copies as many characters as will
                                           // fit in the target buffer; 0 terminator
                                           // may not be copied, but the buffer is
                                           // uselessly completely zeroed if the
                                           // string is shorter!


@KeithThompson:完全不同意,只是为了完整性而添加它。
约翰·博德

16
请注意,这char[8] str = "october";是不好的做法。我必须从字面上算一下我自己,以确保它不是溢出并且在维护中会损坏...例如,如果大小未更新seprateseparate则将拼写错误从纠正为将会中断。
djechlin

1
我同意djechlin的观点,这是不好的做法。JohnBode的答案根本没有评论“不好的实践”方面(这是问题的主要部分!),它只是说明了可以或不能执行的初始化数组的操作。
mastov

未成年人:作为“长度”返回值从strlen()不包括空字符,使用MAX_MONTH_LENGTH保存所需的最大尺寸char string[]往往看起来。错IMO,MAX_MONTH_SIZE效果会更好这里。
chux -恢复莫妮卡

10

我记得的唯一问题是将字符串文字分配给char *

char var1[] = "september";
var1[0] = 'S'; // Ok - 10 element char array allocated on stack
char const *var2 = "september";
var2[0] = 'S'; // Compile time error - pointer to constant string
char *var3 = "september";
var3[0] = 'S'; // Modifying some memory - which may result in modifying... something or crash

例如,使用以下程序:

#include <stdio.h>

int main() {
  char *var1 = "september";
  char *var2 = "september";
  var1[0] = 'S';
  printf("%s\n", var2);
}

当我尝试将其写入标记为只读的页面时,它在我的平台(Linux)上崩溃。在其他平台上,它可能会打印“ September”等。

就是说-通过文字初始化会产生特定的预留量,因此将无法正常工作:

char buf[] = "May";
strncpy(buf, "September", sizeof(buf)); // Result "Sep"

但这会

char buf[32] = "May";
strncpy(buf, "September", sizeof(buf));

最后一句话-我完全不会使用strcpy

char buf[8];
strcpy(buf, "very long string very long string"); // Oops. We overwrite some random memory

尽管某些编译器可以将其更改为安全调用strncpy,但要安全得多:

char buf[1024];
strncpy(buf, something_else, sizeof(buf)); // Copies at most sizeof(buf) chars so there is no possibility of buffer overrun. Please note that sizeof(buf) works for arrays but NOT pointers.
buf[sizeof(buf) - 1] = '\0';

仍然存在缓冲区溢出的风险,strncpy因为当length something_else大于时,它不会null终止复制的字符串sizeof(buf)。我通常设置最后一个字符buf[sizeof(buf)-1] = 0来防止这种情况,或者如果buf将其初始化为零,则将其sizeof(buf) - 1用作复制长度。
syockit '16

使用strlcpystrcpy_ssnprintf至必须使用。
user253751 '18

固定。不幸的是,除非您有大量使用最新编译器的方法(strlcpy并且snprintf不能直接在MSVC上访问,至少在命令上并且strcpy_s不能在* nix上使用),否则没有简便的可移植方式来执行此操作。
Maciej Piechotka

@MaciejPiechotka:好的,感谢上帝,Unix拒绝了微软赞助的附件k。
Deduplicator

6

这两个线程都没有提出的一件事是:

char whopping_great[8192] = "foo";

char whopping_great[8192];
memcpy(whopping_great, "foo", sizeof("foo"));

前者将执行以下操作:

memcpy(whopping_great, "foo", sizeof("foo"));
memset(&whopping_great[sizeof("foo")], 0, sizeof(whopping_great)-sizeof("foo"));

后者仅执行memcpy。C标准坚持认为,如果数组的任何部分都已初始化,则全部被初始化。因此,在这种情况下,最好自己动手做。我认为这可能是大乱斗的结果。

当然

char whopping_big[8192];
whopping_big[0] = 0;

胜过任何一个:

char whopping_big[8192] = {0};

要么

char whopping_big[8192] = "";

ps对于奖励积分,您可以执行以下操作:

memcpy(whopping_great, "foo", (1/(sizeof("foo") <= sizeof(whopping_great)))*sizeof("foo"));

如果您要使数组溢出,则抛出编译时除以零的错误。


5

主要是因为您没有char[]可以在程序中轻松使用的变量/构造的大小。

来自链接的代码示例:

 char string[] = "october";
 strcpy(string, "september");

string在堆栈上分配的长度为7或8个字符。我不记得它是否以这种方式为null终止-您链接到的线程声明它是。

在该字符串上复制“ 9月”显然是内存溢出。

如果传递string给另一个函数,以便另一个函数可以写入数组,则会带来另一个挑战。您需要告诉其他函数数组多长时间,这样不会造成溢出。您可以传递string的结果,strlen()但是该线程说明了如果string不以null终止的话,如何将其炸毁。

最好分配一个固定大小(最好定义为常量)的字符串,然后将数组和固定大小的字符串传递给另一个函数。@John Bode的评论是正确的,并且有减轻这些风险的方法。他们还需要您付出更多的努力才能使用它们。

根据我的经验,对于char[]要放置在其中的其他值,初始化到的值通常太小。使用定义的常数有助于避免该问题。


sizeof string将为您提供缓冲区的大小(8个字节);使用该表达式的结果,而不用strlen担心内存。
同样,你可以做一个检查之前的号召strcpy,看看你的目标缓冲区足够大源字符串:if (sizeof target > strlen(src)) { strcpy (target, src); }
是的,如果必须将数组传递给函数,则还需要传递其物理大小:foo (array, sizeof array / sizeof *array);。– 约翰·波德


2
sizeof string将为您提供缓冲区的大小(8个字节);使用该表达式的结果,而不用strlen担心内存。同样,你可以做一个检查之前的号召strcpy,看看你的目标缓冲区足够大源字符串:if (sizeof target > strlen(src)) { strcpy (target, src); }。是的,如果必须将数组传递给函数,则还需要传递其物理大小:foo (array, sizeof array / sizeof *array);
约翰·博德

1
@JohnBode-谢谢,这些都是要点。我已将您的评论纳入我的回答。

1
更准确地说,大多数对数组名称的引用都会string导致对的隐式转换char*,指向数组的第一个元素。这会丢失数组边界信息。函数调用只是发生这种情况的众多环境之一。char *ptr = string;是另一个。甚至string[0]是一个例子。该[]运算符对指针起作用,而不是直接对数组起作用。建议阅读:comp.lang.c FAQ的第6节。
基思·汤普森

最后,答案实际上是指问题!
mastov

2

我认为“不好的实践”想法来自以下形式:

char string[] = "october is a nice month";

从源计算机代码到堆栈隐式地进行了strcpy。

仅处理指向该字符串的链接更为有效。喜欢与:

char *string = "october is a nice month";

或直接:

strcpy(output, "october is a nice month");

(但是,当然,在大多数代码中,这可能并不重要)


如果您尝试修改它,是否只会复制一个副本?我认为编译器会比这更聪明
Cole Johnson

1
诸如char time_buf[] = "00:00";您将要修改缓冲区的情况如何?一个char *初始化字符串文字设置为第一个字节的地址,所以试图修改它会导致不确定的行为,因为字符串字面量的存储的方法是未知的(实现定义),而修改的字节char[]是完全合法的,因为初始化将字节复制到堆栈上分配的可写空间。在没有详细说明细微差别的情况下说这是“效率较低”或“不良做法” char* vs char[]
Braden Best

-3

从来没有很长时间,但是您应该避免将char []初始化为string,因为“ string”是const char *,并且您将其分配给char *。因此,如果将此char []传递给更改数据的方法,则可能会有有趣的行为。

值得称赞的是,我在char *中混入了一些char [],但这并不好,因为它们有些不同。

将数据分配给char数组没有错,但是由于使用此数组的目的是将其用作'string'(char *),因此很容易忘记您不应该修改此数组。


3
不正确 初始化将字符串文字的内容复制到数组中。const除非您以这种方式定义数组对象,否则它不是。(const尽管C中的字符串文字不是,尽管任何修改字符串文字的尝试的确具有未定义的行为。)char *s = "literal";确实具有您正在谈论的那种行为。最好写成const char *s = "literal";
Keith Thompson

确实是我的错,我将char []与char *混合使用。但是我不确定将内容复制到数组。使用MS C编译器进行的快速检查显示为'char c [] =“ asdf”;' 将在const段中创建“字符串”,然后将此地址分配给数组变量。这实际上就是为什么我说要避免分配给非const char数组的原因。
Dainius

我很怀疑 试试这个程序,让我知道您得到什么输出。
Keith Thompson

2
“而且通常,“ asdf”是一个常量,因此应将其声明为const。” -相同的推理将要求conston int n = 42;,因为它42是一个常数。
Keith Thompson

1
您使用的是什么机器都没有关系。语言标准保证c可修改。它与1 + 1评估结果一样有力的保证2。如果我上面链接的程序除了print以外没有执行其他任何操作EFGH,则表明它是不合格的C实现。
Keith Thompson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.