C语言 我应该使用哪些字符串操作函数?

sczxawaw  于 2022-12-17  发布在  其他
关注(0)|答案(6)|浏览(154)

在我的Windows/Visual C环境中,有很多替代方法可以完成相同的基本字符串操作任务。
例如,对于字符串复制,我可以用途:

  • strcpy,ANSI C标准库函数(CRT)
  • lstrcpy,包含在kernel32.dll中的版本
  • StrCpy,来自Shell轻量级实用程序库
  • StringCchCopy/StringCbCopy,来自“安全字符串”库
  • strcpy_s,CRT的安全增强版

虽然我知道所有这些选择都有历史原因,但我可以为新代码选择一组一致的函数吗?选择哪一个?还是应该根据具体情况选择最合适的函数?

kq0g1dla

kq0g1dla1#

首先,让我们回顾一下每个功能集的优缺点:

ANSI C标准库函数(CRT)

如果你在开发可移植的C代码,像strcpy这样的函数是你唯一的选择。即使在一个纯Windows的项目中,把可移植的代码和依赖于操作系统的代码分开也是一件明智的事情。
这些函数通常具有汇编级优化,因此非常快。
有一些缺点:

  • 它们有许多限制,因此您通常仍必须调用其他库中的函数或提供自己的版本
  • 有一些过时的说法,比如臭名昭著的strncpy

Kernel 32字符串函数

lstrcpy这样的函数是由kernel32导出的,只有在试图避免对CRT的任何依赖时才应该使用。

  • 避免超轻量可执行文件的CRT有效负载(现在不常见,但在90年代不常见!)
  • 避免初始化问题(如果使用CreateThread而不是_beginthread启动线程)。

此外,kernel32函数 * 可以 * 比CRT版本更加优化:当您的可执行文件将在针对酷睿i13优化的Windows 12上运行时,kernel32 * 可以 * 使用程序集优化版本。

Shell轻量级实用程序函数

下面是对kernel32函数的同样考虑,加上一些更复杂的函数的附加值。然而,我怀疑它们是否得到了积极的维护,我将直接跳过它们。

StrSafe函数

StringCchCopy/StringCbCopy函数通常是我个人的选择:它们设计得非常好,功能强大,速度惊人(我还记得一篇白皮书,将这些功能的性能与CRT等效功能进行了比较)。

安全增强型CRT功能

这些函数无疑有着与ANSI C等价物非常相似的优点,因此移植遗留代码是小菜一碟。我特别喜欢基于模板的版本(当然,只有在编译为C++时才可用)。我真的希望它们最终会被标准化。不幸的是,它们有许多缺点:

  • 尽管是一个建议标准,但它们基本上被非Windows社区拒绝(可能只是因为它们来自Microsoft)
  • 当失败时,它们不仅返回错误代码,而且执行无效参数处理程序

结论

虽然我个人最喜欢的Windows开发是StrSafe库,但我的建议是尽可能使用ANSI C函数,因为可移植代码总是一件好事。
在真实的生活中,我开发了一个个性化的可移植库,原型类似于安全增强的CRT函数(包括强大的基于模板的技术),它依赖于Windows上的StrSafe库和其他平台上的ANSI C函数。

mec1mxoz

mec1mxoz2#

对于新的和现有的项目,我个人的偏好是safe字符串库中的StringCchCopy/StringCbCopy版本,我发现这些函数总体上非常一致和灵活,而且它们是从安全性/保密性的Angular 设计的。

d7v8vwbk

d7v8vwbk3#

我的回答略有不同。你想拥有可移植的代码吗?如果你想拥有可移植的代码,除了strcpystrncpy,或者标准的宽字符“string”处理函数之外,你不能依赖其他任何东西。
然后,如果您的代码必须在Windows下运行,则可以使用“安全字符串”变体。
如果你想要便携并且仍然想要一些额外的安全性,那么你应该检查跨平台库,例如gliblibapr或其他“安全字符串库”,例如:SafeStrLibrary

uidvcgyl

uidvcgyl4#

我建议使用标准库中的函数,或者使用跨平台库中的函数。

cgfeq70w

cgfeq70w5#

我会坚持一个,我会选择任何一个是在最有用的库,以防您需要使用更多的它,我会远离kernel32.dll之一,因为它只是窗口。
但这些只是提示,这是一个主观问题。

lsmepo6l

lsmepo6l6#

在这些选择中,我会简单地使用strcpy。至少strcpy_slstrcpy是不应该使用的。研究那些独立编写的库函数可能是值得的,但我会犹豫是否要将非标准库代码作为字符串安全的灵丹妙药。
如果你使用strcpy,你需要确保你的字符串适合目标缓冲区。如果你刚刚给它分配了至少strlen(source)+1的大小,只要源字符串不同时被另一个线程修改就可以了。否则你需要测试它是否适合缓冲区。你可以使用snprintfstrlcpy这样的接口(非标准BSD函数,但易于复制实现),它将截断不适合目标缓冲区的字符串,但是您确实需要评估字符串截断是否会导致其自身的漏洞。我认为测试源字符串是否合适的更好方法是进行新的分配或返回错误状态,而不是执行盲目截断。
如果你要做大量的字符串连接/汇编,你真的应该编写所有的代码来管理长度和当前位置,而不是:

strcpy(out, str1);
strcat(out, str2);
strcat(out, str3);
...

你应该这样做:

size_t l, n = outsize;
char *s = out;

l = strlen(str1);
if (l>=outsize) goto error;
strcpy(s, str1);
s += l;
n -= l;

l = strlen(str2);
if (l>=outsize) goto error;
strcpy(s, str2);
s += l;
n -= l;

...

或者,您可以通过保留size_t类型的当前索引i并使用out+i来避免修改指针,或者您可以通过保留指向缓冲区末尾的指针并执行类似if (l>=end-s) goto error;的操作来避免使用大小变量。
请注意,无论您选择哪种方法,都可以通过编写自己的(简单的)函数来压缩冗余,这些函数获取指向位置/大小变量的指针并调用标准库,例如:

if (!my_strcpy(&s, &n, str1)) goto error;

避免strcat也有性能优势;参见Schlemiel the Painter's algorithm
最后,你应该注意到,在C语言中,有75%的字符串复制和汇编是完全无用的。我的理论是,做这件事的人来自脚本语言的背景,在那里,把字符串放在一起是你一直在做的事情,但在C语言中,这并不经常有用。在许多情况下,你可以完全不复制字符串,而是使用原始副本。同时获得更好的性能和更简单的代码。我想起了最近的一个SO问题,其中OP使用regexec匹配正则表达式,然后复制结果以打印它,类似于:

char *tmp = malloc(match.end-match.start+1);
memcpy(tmp, src+match.start, match.end-match.start);
tmp[match.end-match.start] = 0;
printf("%s\n", tmp);
free(tmp);

同样的事情可以通过以下方式实现:

printf("%.*s\m", match.end-match.start, src+match.start);

没有分配,没有清理,没有错误情况(如果malloc失败,原始代码崩溃)。

相关问题