以下是传统系统中最流行的 strcpy 实现。为什么在开始时不检查 dest 和 src 是否为 NULL?我曾经听说在过去内存有限,所以总是首选短代码。您会在开始时使用 NULL 指针检查来实现 strcpy 和其他类似功能吗?为什么不?
char *strcpy(char *dest, const char *src)
{
char *save = dest;
while(*dest++ = *src++);
return save;
}
NULL
是一个坏指针,但(char*)0x1
. 它也应该检查吗?在我看来(我不知道确切的原因),在如此低级别的操作中进行健全性检查是不必要的。strcpy()
它是如此基础,以至于它应该被视为 asm 指令,如果需要,您应该在调用者中进行自己的健全性检查。只是我的 2 美分 :)
没有健全性检查,因为 C 最重要的潜在意识形态之一是开发人员提供健全性。当您假设开发人员是理智的,您最终会得到一种可以用来在任何地方做任何事情的语言。
这不是一个明确声明的目标——有人很可能想出一个可以检查这一点的实现,等等。也许他们有。但我怀疑许多习惯于 C 的人会叫嚣使用它,因为如果他们的代码有可能被移植到更常用的实现中,他们无论如何都需要进行检查。
整个 C 语言的座右铭是“只要程序员知道他在做什么,我们就会表现得正确”。程序员应该知道做他需要做的所有检查。它不只是检查 NULL,它确保dest
指向足够分配src
的内存来保存,它检查返回值fopen
以确保文件确实成功打开,知道什么时候memcpy
是安全的,什么时候memmove
需要,等等。
检查NULLstrcpy
不会改变语言范式。您仍然需要确保dest
指向足够的空间——这是在不更改界面的情况下strcpy
无法检查的内容。您还需要确保它src
是'\0'
-terminated,这又strcpy
不可能检查。
有一些 C 标准库函数会检查 NULL:例如,free(NULL)
总是安全的。但总的来说,C 希望你知道你在做什么。
[C++ 通常会避开<cstring>
库以支持std::string
和朋友。]
库通常最好让调用者决定它想要的失败语义是什么。strcpy
如果任何一个论点都是,你会怎么做NULL
?默默无闻?失败assert
(在非调试版本中不是一个选项)?
选择加入比选择退出更容易。编写自己的包装器strcpy
来验证输入并改用它是微不足道的。但是,如果库自己执行此操作,您将无法选择不执行这些检查,除非重新实现strcpy
. (例如,您可能已经知道传递给的参数 is strcpy
not NULL
,如果您在紧密循环中调用它或担心最小化功耗,这可能是您关心的事情。)一般来说,它更好在给予更多自由方面犯错(即使这种自由伴随着额外的责任)。
最可能的原因是:因为strcpy
未指定使用NULL
输入(即在这种情况下其行为未定义)。
NULL
那么,如果传入a ,库实现者应该选择做什么呢?我认为最好的办法是让应用程序崩溃。可以这样想:崩溃是出现问题的一个相当明显的迹象……NULL
另一方面,默默地忽略输入可能会掩盖一个更难检测的错误。
NULL 检查没有实现,因为 C 的最早目标支持强大的内存保护。当一个进程试图读取或写入 NULL 时,内存控制器将向 CPU 发出信号,表明尝试了超出范围的内存访问(分段违规),并且内核将终止有问题的进程。
这是一个不错的答案,因为尝试读取或写入 NULL 指针的代码被破坏了;唯一的答案是重新编写代码以检查来自malloc(3)
和朋友的返回值并采取纠正措施。当您尝试使用指向未分配内存的指针时,要就如何解决这种情况做出正确的决定已经太迟了。
根本没有为它定义错误语义。特别是没有办法strcpy
返回错误值。C99 简单地说:
该
strcpy
函数返回 的值s1
。
因此,对于符合要求的实现,甚至不可能返回出现问题的信息。那何必费心呢。
我认为所有这些都是自愿的,因为strcpy
大多数编译器直接被非常高效的汇编器取代。错误检查由调用者决定。
您应该将 C 标准库函数视为汇编代码之上的最薄的附加抽象层,您不希望大量生产这些抽象层来获取您的东西。除此之外的一切,比如错误检查,都是你的责任。
根据我的说法,您想要定义的任何函数都将具有前置条件和后置条件。处理前提条件永远不应该成为函数的一部分。以下是使用从手册页获取的 strcpy 的先决条件。
strcpy() 函数将 src 指向的字符串(包括终止字符 '\0' )复制到 dest 指向的数组中。字符串不能重叠,并且目标字符串 dest 必须足够大以接收副本。
现在,如果不满足前提条件,那么事情可能是不确定的。
我现在是否会在我的 strcpy 中包含一个 NULL 检查。我宁愿有另一个 safe_strcpy,把安全放在首位,我肯定会包括 NULL 检查和处理溢出条件。因此,我的先决条件得到了修改。