为什么strdup被认为是邪恶的?

33
我看到一些海报声称strdup是邪恶的。 对此是否有共识?我已经使用它而没有任何罪恶感,并且看不出为什么它比使用malloc/memcpy更糟。
唯一能想到会让strdup声名狼藉的事情可能是调用者可能会误用它(例如,没有意识到他们必须释放返回的内存;尝试将字符串连接到 strdup'ed 字符串的末尾)。但是,malloc 的字符串也无法免受误用的可能性。
感谢回复并为认为问题无益而道歉(投票关闭)。 总之,回复中似乎没有普遍认为strdup本身就是邪恶的感觉,但是普遍的共识是它可以像 C 的许多其他部分一样被不当或不安全地使用。
其实没有“正确”的答案,但是为了接受一个答案,我接受了@nneoneo的答案 - 它也可以是@R..'s的答案。

这个问题不会是因为我之前的评论而引起的吧? - Seth Carnegie
关于 Can a loop cause issues with assignment in C 的评论。 - dmckee --- ex-moderator kitten
@SethCarnegie 是的,但我在其他地方看到了相同的观点,这就是为什么我创建了一个问题,而不仅仅是问你。 - William Morris
7个回答

37

我能想到的两个原因:

  1. strdup并非严格符合ANSI C标准, 而是遵循POSIX规范。因此, 一些编译器(例如MSVC)不鼓励使用它(MSVC更喜欢使用_strdup), 并且从技术上讲, C标准可以用不同的语义定义自己的strdup, 因为str是一个保留前缀。所以, 在使用时可能会存在一些潜在的可移植性问题。
  2. 它隐藏了内存分配。大多数其他str函数不分配内存,因此用户可能被误导(就像你所说的那样), 认为返回的字符串不需要被释放。

但是,除了这些点之外,我认为仔细地使用strdup是正当的,因为它可以减少代码重复,并为常见习语提供了一个好的实现(例如strdup("constant string") 可以获得一个可变的、可返回的字面字符串的副本)。


strdup()已被纳入C2x标准草案(PDF格式) - pmg

23

我的回答是支持strdup,它不比C语言中的其他任何函数更差。

  1. POSIX是一个标准,如果可移植性成为问题,实现strdup并不太困难。

  2. 是否释放strdup分配的内存不应该成为一个问题,如果有人花点时间阅读手册并理解strdup的工作原理,就不会是一个问题。如果一个人不了解一个函数的工作原理,很可能会出错,这适用于任何函数,而不仅仅是strdup

  3. 在C语言中,内存和大多数其他东西都由程序员管理,所以strdup不比忘记释放malloc分配的内存、未对字符串进行空终止、在scanf中使用错误的格式字符串(从而导致未定义行为)、访问悬挂指针等更差。

(我真的想把这个作为评论发布,但不能只发布一个评论。因此,我将其作为答案发布)。


11

我并没有听说过strdup被描述为邪恶,但是一些人不喜欢它的可能原因有:

  1. 它不是标准的C语言(但在POSIX中可以使用)。然而我认为这个理由很愚蠢,因为只需添加一个几乎是一行的函数即可在缺少它的系统上使用。
  2. 盲目地在各处复制字符串,而不是尽可能在原地使用它们,浪费时间和内存,并引入可能本来没有失败的代码中出现错误的情况。
  3. 当您确实需要复制字符串时,很可能实际上需要更多的空间来修改或构建它,而strdup并不能提供这种功能。

5
我认为大多数对strdup的关注来自于安全方面的担忧,涉及缓冲区溢出和格式不正确的字符串。如果向strdup传递非空终止的字符串,则可能会分配未定义长度的字符串。我不知道这是否可以被具体利用成攻击,但一般来说,只使用具有最大长度限制的字符串函数而不仅是依赖于空字符,这是很好的安全编码实践。

5
如果程序员将一个非以空字符结尾的字符串传递给期望以空字符结尾的函数,那么就犯了一个很大的错误。请注意不要改变原本的意思,只需使其更通俗易懂。 - Jonathan Leffler
1
然而,这种情况确实会发生,这就是在安全性是一个严重问题时使用strncpy而不是strcpy的原因。这也可能发生在意外的用户输入或损坏的文件中。通常最好的安全实践是依赖于显式长度而不是字符串的空终止符。 - john-charles
3
在大多数实际情况下,我不使用 strncpy() 函数。它不能保证空字符的插入。如果你将一个5字节的单词复制到一个20 KiB的缓冲区中,它会写入20475个空字符。这两种行为对我来说都是不可接受的。通常情况下,我确保自己知道字符串的长度,然后使用 memmove() 或者 (偶尔)memcpy() 函数;有时我也会犯错误并使用 strcpy(),但只有在我知道有足够的空间的情况下才这样做。(如果你需要安慰一下,strncat()strncpy() 更糟糕;我从不使用它!)如果我不知道字符串的最大长度,我无法安全地操作它。我甚至无法确定它何时被截断。 - Jonathan Leffler
2
一个没有空终止符的字符数组在定义上不是一个“字符串”。当你给fopen()一个http URL而不是文件路径时,你不会期望它能工作。任何把普通字符数组传递给期望字符串的函数的程序员都应该阅读文档或者不被允许靠近任何生产代码100米以内。他们很可能也会忘记检查malloc()的返回值是否为NULL。 - Secure
1
如果你关心字符串处理的安全性(这是你应该一直关注的),那么最好不要在代码中随意抛出原始的 str* 调用。编写一个关注所有典型问题的字符串处理库,并且专门使用它。当然,如果你更关心使用 strlen() 而不是 my_strlen() 这样的愚蠢事情时的“性能考虑”,那么你会得到你应得的结果。 - Secure
我不确定这个答案是否有意义。strdup保证分配足够的空间来包含终止符。另一种选择是程序员编写news = malloc(strlen(olds) + 1); strcpy(news,olds),并可能忘记了+1(如果他忘记考虑到空字符的需要,使用strncpy也不一定有帮助)。 - dmckee --- ex-moderator kitten

3
许多人显然不这样认为,但是我个人认为strdup有几个问题:
  • 主要问题在于它隐藏了内存分配。其他的str*函数和大多数标准函数后面都不需要free,所以strdup看起来很无害,你可能会忘记清理它。dmckee建议将其添加到需要清理的函数列表中,但是为什么?我并没有看到将两行中等长度缩短为一行的优势。

  • 它总是在堆上分配内存,在C99(是99吗?)的可变长度数组中,你又有了一个使用strcpy(甚至不需要malloc)的理由。你并不总是能这样做,但是当你能够这样做时,你应该这样做。

  • 它不是ISO标准的一部分(但它是POSIX标准的一部分,感谢Wiz),但这只是一个小问题,因为R..提到它可以很容易地被添加。如果你编写可移植的程序,我不确定你如何判断它是否已经定义了......

当然,这些只是我的原因,别人的想法可能不同。回答你的问题,我没有听说过一致的意见。

如果你只是为自己编写程序,并且认为strdup没问题,那么使用它的理由比编写供许多技能水平和年龄段的人阅读的程序要少得多。


1
你的第一个观点基本上否定了整个C语言?如果你不释放strdup(),你就没有释放自己的东西。为什么会有区别呢?对于VLA,特别是在任意大小的字符串上,会带来麻烦和未定义的行为,而且没有警告。至于最后一条:它不是标准的:是的,它是。它是POSIX标准的一部分。它只是ISO C标准的一部分 - 对于大多数人来说足够可移植。 - Wiz
1
@Wiz,你自己的东西很引人注目,而strdup则融入其中。这就是重点。感谢你提到标准性的问题。 - Seth Carnegie
7
我非常想点个踩,因为我不同意你说的大部分内容。虽然我不会这么做,但我要说的是,我认为你的反对意见并不是很相关。考虑到人们模拟strdup()时犯错误的次数之多——通常是忘记为终止空字符分配足够的空间——拥有一个库函数比让每个人都重新发明(7行)函数更加明智。 - Jonathan Leffler
7行代码?我一直认为只有一两行... char *new = malloc(strlen(old)+1); return new ? strcpy(new, old) : 0; - R.. GitHub STOP HELPING ICE
@jjrv 是的,这就是 size_t len = strlen(old) + 1;+1 的原因。 - William Morris
显示剩余3条评论

2
为什么strdup被认为是邪恶的?
1. 与未来语言方向冲突。 2. 依赖于errno状态。 3. 更容易制作自己的strdup(),它不完全像POISX或未来的C2x。

随着C2x的到来,特定包含了strdup(),在那之前使用strdup()会有这些问题。

C2x 提议strdup() 没有提到 errno,而 POSIX 则有。依赖于将 errno 设置为 ENOMEMEINVAL 的代码可能会在将来遇到困难。 C2x 提议char *strdup(const char *s1)const char * 用作参数。用户编码版本的 strdup() 太频繁地使用 char *s1,这会导致一些代码出现错误,因为它们依赖于 char * 签名,如函数指针等。 自己编写的 strdup() 用户代码并没有遵循 C 的未来语言方向,即 "以 str、mem 或 wcs 开头且为小写字母的函数名可以添加到 <string.h> 标头中的声明中",因此可能会与新的 strdup() 和用户的 strdup() 产生库冲突。
如果用户代码在 C2x 之前想要使用 `strdup()` 函数,请考虑将其命名为其他名称,例如 `my_strdup()` 并使用 `const char *` 参数。尽量减少或避免依赖调用返回 `NULL` 后 `errno` 的状态。
我的 `my_strdup()` 努力 - 包括它的缺陷。

1
我的不喜欢strdup的原因是它是一种没有自然对应关系的资源分配。让我们来玩一个傻瓜游戏:我说malloc,你说free。我说open,你说close。我说create,你说destroy。我说strdup,你说……?实际上,strdup的答案当然是free,而这个函数最好被命名为malloc_and_strcpy以使其更清晰。但是许多C程序员并不这样想,并忘记了strdup需要其相反或“结束”free来释放内存。在我的经验中,调用strdup的代码中很常见发现内存泄漏。这是一个奇怪的函数,它结合了strlenmallocstrcpy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接