文章目录

  • 1. 前言
  • 2. 字符串和数组实用程序
    • 2.1. 字符串的表示
    • 2.2. 字符串和数组约定
    • 2.3. 字符串长度
    • 2.4. 复制字符串和数组
    • 2.5. 连接字符串
    • 2.6. 复制时截断字符串
    • 2.7. 字符串/数组比较
    • 2.8. 整理函数
    • 2.9. 搜索函数
      • 2.9.1. 兼容性字符串搜索函数
    • 2.10. 在字符串中查找标记
    • 2.11. 擦除敏感数据
    • 2.12. 洗牌字节
    • 2.13. 混淆数据
    • 2.14. 编码二进制数据
    • 2.15. Argz 和 Envz 向量
      • 2.15.1. Argz 函数
      • 2.15.2. Emvz 函数
  • 3. 参考

1. 前言

The GNU C Library Reference Manual for version 2.35

2. 字符串和数组实用程序

String and Array Utilities

对字符串(以空结尾的字节序列)的操作是许多程序的重要组成部分。GNU C 库提供了一组广泛的字符串实用函数,包括用于复制、连接、比较和搜索字符串的函数。其中许多函数也可以在任意存储区域上运行;例如,memcpy 函数可用于复制任何类型数组的内容。

C 初学者通过在自己的代码中复制此功能来“重新发明轮子”是很常见的,但熟悉库函数并使用它们是值得的,因为这在维护、效率和可移植性方面提供了好处 .

例如,您可以在两行 C 代码中轻松地将一个字符串与另一个字符串进行比较,但如果您使用内置的 strcmp 函数,则出错的可能性较小。而且,由于这些库函数通常经过高度优化,您的程序也可能运行得更快。

2.1. 字符串的表示

Representation of Strings

本节是针对 C 初学者的字符串概念的快速总结。它描述了字符串是如何在 C 中表示的以及一些常见的陷阱。如果您已经熟悉此材料,则可以跳过本节。

字符串是 char 类型的以 null 结尾的字节数组,包括终止的 null 字节。字符串值变量通常声明为 char * 类型的指针。此类变量不包括字符串文本的空间;必须存储在其他地方——数组变量、字符串常量或动态分配的内存(请参阅为程序数据分配存储)。您可以将所选内存空间的地址存储到指针变量中。或者,您可以将空指针存储在指针变量中。空指针不指向任何地方,因此尝试引用它指向的字符串会出错。

多字节字符是一个或多个字节的序列,使用区域设置的编码方案表示单个字符;空字节始终表示空字符。多字节字符串是完全由多字节字符组成的字符串。相反,宽字符串是以空值结尾的 wchar_t 对象序列。宽字符串变量通常被声明为 wchar_t * 类型的指针,类似于字符串变量和 char *。请参阅扩展字符简介。

按照惯例,空字节 ‘\0’ 标记字符串的结尾,空字符 L’\0’ 标记宽字符串的结尾。例如,在测试 char * 变量 p 是否指向标记字符串结尾的空字节时,您可以编写 !*p 或 *p == ‘\0’。

空字节在概念上与空指针完全不同,尽管两者都由整数常量 0 表示。

字符串文字在 C 程序源代码中显示为双引号字符 (‘"’) 之间的多字节字符串。如果初始双引号字符前面紧跟着大写的 ‘L’ (ell) 字符(如 L"foo" ),它是一个宽字符串字面量。字符串字面量也有助于字符串连接:“a” “b” 与 “ab” 相同。对于宽字符串,可以使用 L"a" L"b" 或 L" a" “b”。GNU C 编译器不允许修改字符串文字,因为文字被放置在只读存储中。

声明为 const 的数组也不能修改。将不可修改的字符串指针声明为 const char * 类型通常是一种很好的风格,因为这通常允许 C 编译器检测意外修改并提供一些关于您的程序打算如何处理字符串的文档。

为字节数组分配的内存量可能会超出标记数组包含的字符串结尾的空字节。在本文档中,术语已分配大小始终用于指为数组分配的内存总量,而术语长度指的是直到(但不包括)终止空字节的字节数。宽字符串是相似的,除了它们的大小和长度计算宽字符,而不是字节。

一个臭名昭著的程序错误来源是试图将更多的字节放入字符串中,而不是其分配的大小。在编写扩展字符串或将字节移动到预先分配的数组中的代码时,您应该非常小心地跟踪文本的长度并明确检查数组是否溢出。许多库函数不为您执行此操作!还请记住,您需要分配一个额外的字节来保存标记字符串结尾的空字节。

最初的字符串是字节序列,其中每个字节代表一个字符。如果使用单字节字符编码对字符串进行编码,今天仍然如此。如果使用多字节编码对字符串进行编码,情况就不同了(有关编码的更多信息,请参阅扩展字符简介)。这两种字符串的编程接口没有区别;程序员必须意识到这一点并相应地解释字节序列。

但是由于没有单独的接口处理这些差异,基于字节的字符串函数有时很难使用。由于这些函数的计数参数指定字节,调用 memcpy 可能会在中间剪切一个多字节字符,并将一个不完整(因此不可用)的字节序列放入目标缓冲区。

为了避免这些问题,后来的 ISO C 标准版本引入了第二组对宽字符进行操作的函数(参见扩展字符简介)。这些函数没有单字节版本的问题,因为每个宽字符都是合法的、可解释的值。这并不意味着在任意点切割宽字符串没有问题。它通常适用于基于字母的语言(非规范化文本除外),但基于音节的语言仍然存在一个问题,即完成一个逻辑单元需要多个宽字符。这是 C 库函数无法解决的更高级别的问题。但至少不能创建无效的字节序列是好的。此外,与多字节字符相比,更高级的函数也可以更容易地对宽字符进行操作,因此一个常见的策略是在文本不仅仅是简单复制时在内部使用宽字符。

2.2. 字符串和数组约定

String and Array Conventions

本章描述了在任意数组或内存块上工作的函数,以及特定于字符串和宽字符串的函数。

对任意内存块进行操作的函数的名称以“mem”和“wmem”开头(例如 memcpy 和 wmemcpy),并且总是采用一个参数来指定要操作的内存块的大小(分别以字节和宽字符为单位)在。这些函数的数组参数和返回值的类型为 void * 或 wchar_t。作为风格问题,与“mem”函数一起使用的数组元素被称为“字节”。您可以将任何类型的指针传递给这些函数,并且 sizeof 运算符在计算 size 参数的值时很有用。“wmem”函数的参数必须是 wchar_t * 类型。除了这种类型的数组,这些函数实际上不能用于任何东西。

相比之下,专门对字符串和宽字符串进行操作的函数的名称分别以“str”和“wcs”开头(例如 strcpy 和 wcscpy),并查找终止空字节或空宽字符,而不需要显式的大小参数被通过。(其中一些函数接受指定的最大长度,但它们也检查提前终止。)这些函数的数组参数和返回值分别具有 char * 和 wchar_t * 类型,并且数组元素被称为“字节”和“宽字符”。

在许多情况下,函数同时存在“mem”和“str”/“wcs”两种版本。哪个更适合使用取决于具体情况。当您的程序操作任意数组或存储块时,您应该始终使用“mem”函数。另一方面,当你处理字符串时,使用‘str’/‘wcs’函数通常更方便,除非你事先已经知道字符串的长度。“wmem”函数应该用于已知大小的宽字符数组。

一些内存和字符串函数将单个字符作为参数。由于 char 类型的值在用作参数时会自动提升为 int 类型的值,因此函数使用 int 声明为相关参数的类型。对于宽字符函数,情况类似:单个宽字符的参数类型是 wint_t 而不是 wchar_t。这对于许多实现来说是不必要的,因为 wchar_t 足够大而不会被自动提升,但由于 ISO C 标准不需要这样的类型选择,所以使用了 wint_t 类型。

2.3. 字符串长度

String Length

您可以使用 strlen 函数获取字符串的长度。该函数在头文件 string.h 中声明。

函数:size_t strlen (const char *s)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strlen 函数以字节为单位返回字符串 s 的长度。(换句话说,它返回数组中终止空字节的偏移量。)

例如,

strlen ("hello, world")⇒ 12

当应用于数组时,strlen 函数返回存储在那里的字符串的长度,而不是分配的大小。您可以使用 sizeof 运算符获取包含字符串的数组的分配大小:

char string[32] = "hello, world";
sizeof (string)⇒ 32
strlen (string)⇒ 12

但请注意,除非字符串是数组本身,而不是指向它的指针,否则这将不起作用。例如:

char string[32] = "hello, world";
char *ptr = string;
sizeof (string)⇒ 32
sizeof (ptr)⇒ 4  /* (on a machine with 4 byte pointers) */

当您使用带有字符串参数的函数时,这是一个容易犯的错误。这些参数始终是指针,而不是数组。

还必须注意,对于多字节编码的字符串,返回值不必与字符串中的字符数相对应。要获取此值,可以将字符串转换为宽字符,并且可以使用 wcslen 或者可以使用类似以下代码的内容:

/* The input is in string.The length is expected in n.  */
{mbstate_t t;char *scopy = string;/* In initial state.  */memset (&t, '\0', sizeof (t));/* Determine number of characters.  */n = mbsrtowcs (NULL, &scopy, strlen (scopy), &t);
}

如果经常需要字符数(而不是字节数),这样做很麻烦,最好使用宽字符。

等价的宽字符在 wchar.h 中声明。

函数:size_t wcslen (const wchar_t *ws)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcslen 函数是等价于 strlen 的宽字符。返回值是ws指向的宽字符串中宽字符的个数(这也是ws的终止空宽字符的偏移量)。

由于没有构成一个宽字符的多宽字符序列,因此返回值不仅是数组中的偏移量,还是宽字符的数量。

此功能是在 ISO C90 的修订 1 中引入的。

函数:size_t strnlen (const char *s, size_t maxlen)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

如果大小为 maxlen 的数组 s 包含一个空字节,则 strnlen 函数以字节为单位返回字符串 s 的长度。否则返回 maxlen。因此,这个函数等价于 (strlen (s) < maxlen ? strlen (s) : maxlen) 但它更有效并且即使 s 不是以 null 结尾的,只要 maxlen 不超过 s 数组的大小,它也可以工作。

char string[32] = "hello, world";
strnlen (string, 32)⇒ 12
strnlen (string, 5)⇒ 5

该函数是 GNU 扩展,在 string.h 中声明。

函数:size_t wcsnlen (const wchar_t *ws, size_t maxlen)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcsnlen 是等价于 strnlen 的宽字符。maxlen 参数指定宽字符的最大数量。

该函数是 GNU 扩展,在 wchar.h 中声明。

2.4. 复制字符串和数组

Copying Strings and Arrays

您可以使用本节中描述的函数来复制字符串、宽字符串和数组的内容。“str”和“mem”函数在 string.h 中声明,而“w”函数在 wchar.h 中声明。

记住本节中函数参数顺序的一种有用方法是,它对应于赋值表达式,目标数组指定在源数组的左侧。这些函数中的大多数返回目标数组的地址;一些返回目的地终止空值的地址,或刚刚过去的目的地。

如果源数组和目标数组重叠,这些函数中的大多数都不能正常工作。例如,如果目标数组的开头与源数组的结尾重叠,则源数组该部分的原始内容可能会在被复制之前被覆盖。更糟糕的是,在字符串函数的情况下,标记字符串结尾的空字节可能会丢失,并且复制函数可能会陷入循环,从而破坏分配给程序的所有内存。

本手册中明确指出了在重叠数组之间复制时出现问题的所有函数。除了本节中的函数之外,还有一些其他函数,例如 sprintf(请参阅格式化输出函数)和 scanf(请参阅格式化输入函数)。

函数:void * memcpy (void *restrict to, const void *restrict from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

memcpy 函数将 size 字节从以 from 开头的对象复制到以 to 开头的对象中。如果往返的两个数组重叠,则此函数的行为未定义;如果可能重叠,请改用 memmove。

memcpy 返回的值就是 to 的值。

这是一个如何使用 memcpy 复制数组内容的示例:

struct foo *oldarray, *newarray;
int arraysize;
…
memcpy (new, old, arraysize * sizeof (struct foo));

函数:wchar_t * wmemcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wmemcpy 函数将从以 wfrom 开始的对象复制 size 宽字符到以 wto 开始的对象中。如果两个数组 wto 和 wfrom 重叠,则此函数的行为未定义;如果可以重叠,请改用 wmemmove。

以下是 wmemcpy 的可能实现,但可能有更多优化。

wchar_t *
wmemcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,size_t size)
{return (wchar_t *) memcpy (wto, wfrom, size * sizeof (wchar_t));
}

wmemcpy 返回的值就是 wto 的值。

此功能是在 ISO C90 的修订 1 中引入的。

函数:void * mempcpy (void *restrict to, const void *restrict from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

memcpy 函数与 memcpy 函数几乎相同。它将 size 字节从 from 开始的对象复制到 to 指向的对象中。但不是返回 to 的值,而是返回一个指针,指向对象中从 to 开始的最后一个写入字节之后的字节。即,值为 ((void *) ((char *) to + size))。

此功能在应将多个对象复制到连续内存位置的情况下很有用。

void *
combine (void *o1, size_t s1, void *o2, size_t s2)
{void *result = malloc (s1 + s2);if (result != NULL)mempcpy (mempcpy (result, o1, s1), o2, s2);return result;
}

这个函数是一个 GNU 扩展。

函数:wchar_t * wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wmemcpy 函数与 wmemcpy 函数几乎相同。它将从 wfrom 开始的对象复制 size 宽字符到 wto 指向的对象中。但是,它不是返回 wto 的值,而是返回一个指向对象中最后一个写入的宽字符之后的宽字符的指针,该宽字符从 wto 开始。即,值是 wto + size。

此功能在应将多个对象复制到连续内存位置的情况下很有用。

以下是 wmemcpy 的可能实现,但可能有更多优化。

wchar_t *
wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,size_t size)
{return (wchar_t *) mempcpy (wto, wfrom, size * sizeof (wchar_t));
}

这个函数是一个 GNU 扩展。

函数:void * memmove (void *to, const void *from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

memmove 将 from 的 size 字节复制到 to 的 size 字节,即使这两个空间块重叠。在重叠的情况下,memmove 会小心地复制 from 块中字节的原始值,包括也属于 at 块的那些字节。

memmove 返回的值就是 to 的值。

函数:wchar_t * wmemmove (wchar_t *wto, const wchar_t *wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wmemmove 将 wfrom 的 size 宽字符复制到 wto 的 size 宽字符中,即使这两个空间块重叠。在重叠的情况下,wmemmove 会小心地复制 wfrom 块中宽字符的原始值,包括那些也属于 wto 块的宽字符。

以下是 wmemcpy 的可能实现,但可能有更多优化。

wchar_t *
wmempcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom,size_t size)
{return (wchar_t *) mempcpy (wto, wfrom, size * sizeof (wchar_t));
}

wmemmove 返回的值就是 wto 的值。

这个函数是一个 GNU 扩展。

函数:void * memccpy (void *restrict to, const void *restrict from, int c, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数从 from 到 to 复制不超过 size 个字节,如果找到匹配 c 的字节则停止。返回值是指向 c 被复制后一个字节的指针,或者如果在 from 的第一个 size 字节中没有出现与 c 匹配的字节,则返回空指针。

函数:void * memset (void *block, int c, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将 c 的值(转换为无符号字符)复制到从 block 开始的对象的每个前 size 字节中。它返回块的值。

函数:wchar_t * wmemset (wchar_t *block, wchar_t wc, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数将 wc 的值复制到从块开始的对象的每个第一个 size 宽字符中。它返回块的值。

函数:char * strcpy (char *restrict to, const char *restrict from)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这会将字符串中的字节从(直到并包括终止的空字节)复制到字符串中。与 memcpy 一样,如果字符串重叠,此函数会产生未定义的结果。返回值是to的值。

函数:wchar_t * wcscpy (wchar_t *restrict wto, const wchar_t *restrict wfrom)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这会将宽字符串 wfrom 中的宽字符(直到并包括终止的空宽字符)复制到字符串 wto 中。与 wmemcpy 一样,如果字符串重叠,此函数会产生未定义的结果。返回值是 wto 的值。

函数:char * strdup (const char *s)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

该函数将字符串 s 复制到新分配的字符串中。字符串是使用 malloc 分配的;请参阅无约束分配。如果 malloc 无法为新字符串分配空间,则 strdup 返回一个空指针。否则它返回一个指向新字符串的指针。

函数:wchar_t * wcsdup (const wchar_t *ws)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

此函数将宽字符串 ws 复制到新分配的字符串中。字符串是使用 malloc 分配的;请参阅无约束分配。如果 malloc 无法为新字符串分配空间,wcsdup 将返回一个空指针。否则它返回一个指向新宽字符串的指针。

这个函数是一个 GNU 扩展。

函数:char * stpcpy (char *restrict to, const char *restrict from)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这个函数类似于strcpy,只是它返回一个指向字符串结尾的指针(即终止空字节到+strlen(from)的地址)而不是开头。

例如,该程序使用 stpcpy 连接“foo”和“bar”以生成“foobar”,然后将其打印出来。

#include <string.h>
#include <stdio.h>int
main (void)
{char buffer[10];char *to = buffer;to = stpcpy (to, "foo");to = stpcpy (to, "bar");puts (buffer);return 0;
}

此函数是 POSIX.1-2008 及更高版本的一部分,但早在标准化之前就已在 GNU C 库和其他系统中作为扩展提供。

如果字符串重叠,它的行为是不确定的。该函数在 string.h 中声明。

函数:wchar_t * wcpcpy (wchar_t *restrict wto, const wchar_t *restrict wfrom)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数与 wcscpy 类似,不同之处在于它返回指向字符串 wto 结尾的指针(即终止空宽字符 wto + wcslen (wfrom) 的地址)而不是开头。

此函数不是 ISO 或 POSIX 的一部分,但在开发 GNU C 库本身时发现它很有用。

如果字符串重叠,wcpcpy 的行为是未定义的。

wcpcpy 是一个 GNU 扩展,在 wchar.h 中声明。

函数:char * strdupa (const char *s)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此宏类似于 strdup,但使用 alloca 而不是 malloc 分配新字符串(请参阅具有可变大小的自动存储)。这当然意味着返回的字符串与使用 alloca 分配的任何内存块具有相同的限制。

出于显而易见的原因,strdupa 仅作为宏实现;你不能得到这个函数的地址。尽管有这个限制,但它是一个有用的功能。下面的代码显示了使用 malloc 会更加昂贵的情况。

#include <paths.h>
#include <string.h>
#include <stdio.h>const char path[] = _PATH_STDPATH;int
main (void)
{char *wr_path = strdupa (path);char *cp = strtok (wr_path, ":");while (cp != NULL){puts (cp);cp = strtok (NULL, ":");}return 0;
}

请注意,直接使用 path 调用 strtok 是无效的。也不允许在 strtok 的参数列表中调用 strdupa,因为 strdupa 使用 alloca(请参阅具有可变大小的自动存储)会干扰参数传递。

此功能仅在使用 GNU CC 时可用。

函数:void bcopy (const void *from, void *to, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这是从 BSD 派生的 memmove 的部分过时替代方案。请注意,它并不完全等同于 memmove,因为参数的顺序不同,并且没有返回值。

函数:void bzero (void *block, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这是从 BSD 派生的 memset 的部分过时替代方案。请注意,它不像 memset 那样通用,因为它可以存储的唯一值是零。

2.5. 连接字符串

Concatenating Strings

本节中描述的函数将字符串或宽字符串的内容连接到另一个。它们遵循约定中的字符串复制功能。请参阅复制字符串和数组。“strcat”在头文件 string.h 中声明,而“wcscat”在 wchar.h 中声明。

函数:char * strcat (char *restrict to, const char *restrict from)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strcat 函数与 strcpy 类似,只是来自 from 的字节被连接或附加到 to 的末尾,而不是覆盖它。也就是说,from 的第一个字节会覆盖标记 to 结束的空字节。

strcat 的等效定义是:

char *
strcat (char *restrict to, const char *restrict from)
{strcpy (to + strlen (to), from);return to;
}

如果字符串重叠,则此函数具有未定义的结果。

如下所述,此函数存在严重的性能问题。

函数:wchar_t * wcscat (wchar_t *restrict wto, const wchar_t *restrict wfrom)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcscat 函数与 wcscpy 类似,不同之处在于 wfrom 的宽字符被连接或附加到 wto 的末尾,而不是覆盖它。也就是说,来自 wfrom 的第一个宽字符会覆盖标记 wto 结束的空宽字符。

wcscat 的等效定义是:

wchar_t *
wcscat (wchar_t *wto, const wchar_t *wfrom)
{wcscpy (wto + wcslen (wto), wfrom);return wto;
}

如果字符串重叠,则此函数具有未定义的结果。

如下所述,此函数存在严重的性能问题。

使用 strcat 或 wcscat 函数(或稍后部分中定义的 strncat 或 wcsncat 函数)的程序员很容易被认为是懒惰和鲁莽的。在几乎所有情况下,参与字符串的长度都是已知的(最好应该是这样,否则如何确保分配的缓冲区大小足够?)或者至少,如果跟踪结果,可以知道它们。各种函数调用。但是使用 strcat/wcscat 是非常低效的。寻找目标字符串的结尾浪费了大量时间,以便可以开始实际的复制。这是一个常见的例子:

/* This function concatenates arbitrarily many strings.  The lastparameter must be NULL.  */
char *
concat (const char *str, …)
{va_list ap, ap2;size_t total = 1;va_start (ap, str);va_copy (ap2, ap);/* Determine how much space we need.  */for (const char *s = str; s != NULL; s = va_arg (ap, const char *))total += strlen (s);va_end (ap);char *result = malloc (total);if (result != NULL){result[0] = '\0';/* Copy the strings.  */for (s = str; s != NULL; s = va_arg (ap2, const char *))strcat (result, s);}va_end (ap2);return result;
}

这看起来很简单,尤其是实际复制字符串的第二个循环。但是这些无辜的行隐藏了主要的性能损失。试想一下,必须连接十个 100 字节的字符串。对于第二个字符串,我们在已经存储的 100 个字节中搜索字符串的结尾,以便我们可以附加下一个字符串。对于所有字符串,找到中间结果结尾所需的比较总计为 5500!如果我们将复制与分配搜索结合起来,我们可以更有效地编写这个函数:

char *
concat (const char *str, …)
{size_t allocated = 100;char *result = malloc (allocated);if (result != NULL){va_list ap;size_t resultlen = 0;char *newp;va_start (ap, str);for (const char *s = str; s != NULL; s = va_arg (ap, const char *)){size_t len = strlen (s);/* Resize the allocated memory if necessary.  */if (resultlen + len + 1 > allocated){allocated += len;newp = reallocarray (result, allocated, 2);allocated *= 2;if (newp == NULL){free (result);return NULL;}result = newp;}memcpy (result + resultlen, s, len);resultlen += len;}/* Terminate the result string.  */result[resultlen++] = '\0';/* Resize memory to the optimal size.  */newp = realloc (result, resultlen);if (newp != NULL)result = newp;va_end (ap);}return result;
}

有了更多关于输入字符串的知识,就可以微调内存分配。我们在这里指出的区别是我们不再使用 strcat。我们总是跟踪当前中间结果的长度,这样我们就可以节省对字符串结尾的搜索并使用 mempcpy。请注意,我们也不使用 stpcpy,因为我们正在处理字符串,这可能看起来更自然。但这不是必需的,因为我们已经知道字符串的长度,因此可以使用更快的内存复制功能。该示例同样适用于宽字符。

每当程序员觉得需要使用 strcat 时,她或他都应该三思而后行,查看程序是否无法重写代码以利用已经计算的结果。再说一遍:几乎总是不需要使用 strcat。

2.6. 复制时截断字符串

Truncating Strings while Copying

本节中描述的函数将字符串或数组的可能被截断的内容复制或连接到另一个,对于宽字符串也是如此。它们遵循标头约定中的字符串复制功能。请参阅复制字符串和数组。“str”函数在头文件 string.h 中声明,“wc”函数在文件 wchar.h 中声明。

函数:char * strncpy (char *restrict to, const char *restrict from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 strcpy,但总是将 size 字节精确复制到 to。

如果 from 在其第一个 size 字节中不包含空字节,则 strncpy 仅复制第一个 size 字节。在这种情况下,不会将空终止符写入 to。

否则 from 必须是长度小于 size 的字符串。在这种情况下,strncpy 复制所有 from,然后是足够的空字节以加起来总共 size 个字节。

如果字符串重叠,则 strncpy 的行为未定义。

此函数是为现在很少使用的数组设计的,该数组由非空字节后跟零个或多个空字节组成。它需要设置目标的所有 size 字节,即使 size 远大于 from 的长度。如下所述,此函数通常不是处理文本的最佳选择。

函数:wchar_t * wcsncpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 wcscpy,但总是将精确大小的宽字符复制到 wto。

如果 wfrom 在其第一个 size 宽字符中不包含 null 宽字符,则 wcsncpy 仅复制第一个 size 宽字符。在这种情况下,不会将空终止符写入 wto。

否则 wfrom 必须是长度小于 size 的宽字符串。在这种情况下,wcsncpy 复制所有 wfrom,然后是足够的空宽字符,以加起来总共 size 宽字符。

如果字符串重叠,则 wcsncpy 的行为未定义。

此函数是 strncpy 的宽字符对应物,并且遇到了 strncpy 所做的大多数问题。例如,如下所述,此函数通常不是处理文本的糟糕选择。

函数:char * strndup (const char *s, size_t size)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

此函数类似于 strdup,但总是将最多 size 个字节复制到新分配的字符串中。

如果 s 的长度大于 size,则 strndup 仅复制第一个 size 字节并添加一个结束空字节。否则复制所有字节并终止字符串。

此函数与 strncpy 的不同之处在于它始终终止目标字符串。

如下所述,此函数通常不是处理文本的最佳选择。

strndup 是一个 GNU 扩展。

函数:char * strndupa (const char *s, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 strndup,但与 strdupa 一样,它使用 alloca 分配新字符串,请参阅具有可变大小的自动存储。strdupa 的相同优点和限制也适用于 strndupa。

此函数仅作为宏实现,就像 strdupa 一样。就像 strdupa 一样,这个宏也不能在函数调用的参数列表中使用。

如下所述,此函数通常不是处理文本的最佳选择。

strndupa 仅在使用 GNU CC 时可用。

函数:char * stpncpy (char *restrict to, const char *restrict from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 stpcpy,但总是将 size 字节复制到 to。

如果 from 的长度大于 size,则 stpncpy 仅复制第一个 size 字节,并返回指向紧跟最后复制的字节的指针。请注意,在这种情况下,没有写入空终止符。

如果 from 的长度小于 size,则 stpncpy 复制所有 from,后跟足够的空字节以总计为 size 字节。这种行为很少有用,但它被实现为在使用 strncpy 的这种行为的上下文中很有用。stpncpy 返回指向第一个写入的空字节的指针。

此函数不是 ISO 或 POSIX 的一部分,但在开发 GNU C 库本身时发现它很有用。

如果字符串重叠,它的行为是不确定的。该函数在 string.h 中声明。

如下所述,此函数通常不是处理文本的最佳选择。

函数:wchar_t * wcpncpy (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 wcpcpy,但总是将 wsize 宽字符精确地复制到 wto 中。

如果 wfrom 的长度大于 size,则 wcpncpy 仅复制第一个 size 宽字符,并返回指向紧跟最后复制的最后一个非空宽字符之后的宽字符的指针。请注意,在这种情况下,wto 中没有写入空终止符。

如果 wfrom 的长度小于 size,则 wcpncpy 复制所有 wfrom,然后是足够的空宽字符,以加起来总共 size 宽字符。这种行为很少有用,但它被实现为在使用 wcsncpy 的这种行为的上下文中很有用。wcpncpy 返回指向第一个写入的空宽字符的指针。

此函数不是 ISO 或 POSIX 的一部分,但在开发 GNU C 库本身时发现它很有用。

如果字符串重叠,它的行为是不确定的。

如下所述,此函数通常不是处理文本的最佳选择。

wcpncpy 是一个 GNU 扩展。

函数:char * strncat (char *restrict to, const char *restrict from, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数与 strcat 类似,不同之处在于将 from from 的 size 个字节附加到 to 的末尾,并且 from 不需要以 null 结尾。单个空字节也总是附加到 to,因此 to 的总分配大小必须至少比其初始长度长 size + 1 个字节。

strncat 函数可以这样实现:

char *
strncat (char *to, const char *from, size_t size)
{size_t len = strlen (to);memcpy (to + len, from, strnlen (from, size));to[len + strnlen (from, size)] = '\0';return to;
}

如果字符串重叠,则 strncat 的行为未定义。

作为 strncpy 的伴侣,strncat 是为现在很少使用的数组设计的,该数组由非空字节后跟零个或多个空字节组成。如下所述,此函数通常不是处理文本的最佳选择。此外,此功能存在严重的性能问题。请参阅连接字符串。

函数:wchar_t * wcsncat (wchar_t *restrict wto, const wchar_t *restrict wfrom, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数与 wcscat 类似,不同之处在于将不超过 size 的宽字符 from from 附加到 to 的末尾,并且 from 不需要以 null 结尾。单个空宽字符也总是附加到 to,因此 to 的总分配大小必须至少比其初始长度长 wcsnlen (wfrom, size) + 1 个宽字符。

wcsncat 函数可以这样实现:

wchar_t *
wcsncat (wchar_t *restrict wto, const wchar_t *restrict wfrom,size_t size)
{size_t len = wcslen (wto);memcpy (wto + len, wfrom, wcsnlen (wfrom, size) * sizeof (wchar_t));wto[len + wcsnlen (wfrom, size)] = L'\0';return wto;
}

如果字符串重叠,wcsncat 的行为是未定义的。

如下所述,此函数通常不是处理文本的最佳选择。此外,此功能存在严重的性能问题。请参阅连接字符串。

因为这些函数可以突然截断字符串或​​宽字符串,所以它们通常不是处理文本的糟糕选择。在处理或连接多字节字符串时,它们可以在多字节字符内截断,因此结果不是有效的多字节字符串。在组合或连接多字节或宽字符串时,它们可能会在组合字符后截断输出,从而导致字素损坏。即使在处理单字节字符串时,它们也可能导致错误:例如,在计算纯 ASCII 用户名时,截断的名称可以识别错误的用户。

尽管可以通过手动将复制函数调用替换为截断函数调用来防止某些缓冲区溢出,但通常有更简单、更安全的自动技术可以导致缓冲区溢出可靠地终止程序,例如 GCC 的 -fcheck-pointer-bounds 和 -fsanitize =地址选项。请参阅使用 GCC 中的调试程序或 GCC 的选项。因为截断函数可以掩盖应用程序的错误,否则这些错误会被自动技术捕获,因此只有在应用程序的底层逻辑需要截断时才应该使用这些函数。

注意:GNU 程序不应截断字符串或​​宽字符串以适应任意大小限制。请参阅在 GNU 编码标准中编写健壮的程序。通常最好使用动态内存分配(参见无约束分配)和诸如 strdup 或 asprintf 之类的函数来构造字符串,而不是字符串截断函数。

2.7. 字符串/数组比较

String/Array Comparison

您可以使用本节中的函数对字符串和数组的内容进行比较。除了检查相等性之外,这些函数还可以用作排序操作的排序函数。有关此示例,请参阅搜索和排序。

与 C 中的大多数比较操作不同,字符串比较函数在字符串不等价时返回非零值,而不是如果它们等价。值的符号表示不等价的字符串的第一部分的相对顺序:负值表示第一个字符串“小于”第二个,而正值表示第一个字符串“大于” .

这些函数最常见的用途是仅检查是否相等。这通常是用像’! strcmp (s1, s2)'。

所有这些函数都在头文件 string.h 中声明。

函数:int memcmp (const void *a1, const void *a2, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 memcmp 比较从 a1 开始的内存大小字节与从 a2 开始的内存大小字节。返回的值与第一个不同的字节对之间的差异具有相同的符号(解释为 unsigned char 对象,然后提升为 int)。

如果两个块的内容相等,则 memcmp 返回 0。

函数:int wmemcmp (const wchar_t *a1, const wchar_t *a2, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 wmemcmp 比较从 a1 开始的大小宽字符与从 a2 开始的大小宽字符。返回的值小于还是大于零取决于第一个不同的宽字符是 a1 是小于还是大于 a2 中对应的宽字符。

如果两个块的内容相等,则 wmemcmp 返回 0。

在任意数组上,memcmp 函数主要用于测试相等性。对字节以外的事物数组进行按字节排序的比较通常没有意义。例如,对构成浮点数的字节进行逐字节比较可能无法告诉您有关浮点数值之间关系的任何信息。

wmemcmp 实际上只对比较 wchar_t 类型的数组有用,因为该函数一次查看 sizeof (wchar_t) 个字节,而这个字节数取决于系统。

您还应该小心使用 memcmp 比较可能包含“空洞”的对象,例如插入到结构对象中以强制对齐要求的填充、联合末尾的额外空间以及长度为的字符串末尾的额外字节小于他们分配的大小。这些“漏洞”的内容是不确定的,在进行逐字节比较时可能会导致奇怪的行为。要获得更可预测的结果,请执行明确的组件比较。

例如,给定一个结构类型定义,如:

struct foo{unsigned char tag;union{double f;long i;char *p;} value;};

您最好编写一个专门的比较函数来比较 struct foo 对象,而不是将它们与 memcmp 进行比较。

函数:int strcmp (const char *s1, const char *s2)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strcmp 函数将字符串 s1 与 s2 进行比较,返回一个与第一个不同字节对之间的差具有相同符号的值(解释为 unsigned char 对象,然后提升为 int)。

如果两个字符串相等,则 strcmp 返回 0。

strcmp 使用排序的结果是,如果 s1 是 s2 的初始子字符串,则认为 s1“小于”s2。

strcmp 不考虑写入字符串的语言的排序约定。要得到那个必须使用strcoll。

函数:int wcscmp (const wchar_t *ws1, const wchar_t *ws2)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcscmp 函数将宽字符串 ws1 与 ws2 进行比较。返回的值小于还是大于零取决于第一个不同的宽字符是 ws1 是小于还是大于 ws2 中相应的宽字符。

如果两个字符串相等,wcscmp 返回 0。

wcscmp 使用的排序结果是,如果 ws1 是 ws2 的初始子字符串,则 ws1 被认为“小于”ws2。

wcscmp 不考虑写入字符串的语言的排序约定。要获得它,必须使用 wcscoll。

函数:int strcasecmp (const char *s1, const char *s2)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 strcmp,只是忽略了大小写的差异,并且它的参数必须是多字节字符串。大写和小写字符的关联方式由当前选择的语言环境决定。在标准的“C”语言环境中,字符 Ä 和 ä 不匹配,但在将这些字符视为它们匹配的字母的一部分的语言环境中。

strcasecmp 源自 BSD。

函数:int wcscasecmp (const wchar_t *ws1, const wchar_t *ws2)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 wcscmp,只是忽略了大小写的差异。大写和小写字符的关联方式由当前选择的语言环境决定。在标准的“C”语言环境中,字符 Ä 和 ä 不匹配,但在将这些字符视为它们匹配的字母的一部分的语言环境中。

wcscasecmp 是一个 GNU 扩展。

函数:int strncmp (const char *s1, const char *s2, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数与 strcmp 类似,只是比较的字节数不超过 size。换句话说,如果两个字符串的第一个大小字节相同,则返回值为零。

函数:int wcsncmp (const wchar_t *ws1, const wchar_t *ws2, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 wcscmp,不同之处在于比较不超过 size 的宽字符。换句话说,如果两个字符串的第一个大小宽字符相同,则返回值为零。

函数:int strncasecmp (const char *s1, const char *s2, size_t n)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 strncmp,只是忽略大小写的差异,并且参数的比较部分应由有效的多字节字符组成。与 strcasecmp 一样,它取决于区域设置,大写和小写字符的关联方式。

strncasecmp 是一个 GNU 扩展。

函数:int wcsncasecmp (const wchar_t *ws1, const wchar_t *s2, size_t n)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数类似于 wcsncmp,只是忽略了大小写的差异。与 wcscasecmp 一样,它取决于区域设置,大写和小写字符的关联方式。

wcsncasecmp 是一个 GNU 扩展。

下面是一些示例,展示了 strcmp 和 strncmp 的使用(可以为宽字符函数构造等效示例)。这些示例假定使用 ASCII 字符集。(如果使用其他字符集(例如 EBCDIC),则字形与不同的数字代码相关联,并且返回值和顺序可能不同。)

strcmp ("hello", "hello")⇒ 0    /* These two strings are the same. */
strcmp ("hello", "Hello")⇒ 32   /* Comparisons are case-sensitive. */
strcmp ("hello", "world")⇒ -15  /* The byte 'h' comes before 'w'. */
strcmp ("hello", "hello, world")⇒ -44  /* Comparing a null byte against a comma. */
strncmp ("hello", "hello, world", 5)⇒ 0    /* The initial 5 bytes are the same. */
strncmp ("hello, world", "hello, stupid world!!!", 5)⇒ 0    /* The initial 5 bytes are the same. */

函数:int strverscmp (const char *s1, const char *s2)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strverscmp 函数将字符串 s1 与 s2 进行比较,将它们视为保存索引/版本号。返回值遵循与 strcmp 函数中相同的约定。事实上,如果 s1 和 s2 不包含数字,则 strverscmp 的行为类似于 strcmp(在某种意义上,结果的符号相同)。

strverscmp 函数实现的比较算法与其他版本比较算法略有不同。该实现基于有限状态机,其行为近似如下。

每个输入字符串都被分成非数字和数字的序列。这些序列在字符串的开头和结尾可以为空。数字由 isdigit 函数确定,因此受当前语言环境的影响。

比较从(可能为空的)非数字序列开始。非数字或数字的第一个不相等序列决定了比较的结果。

如果两个字符串中的相应非数字序列的长度相等,则按字典顺序进行比较。如果长度不同,则将较短的非数字序列扩展为紧随其后的输入字符串字符(可能是空终止符),另一个序列被截断为相同(扩展)长度,这两个序列是按字典顺序比较。在最后一种情况下,序列比较确定函数的结果,因为扩展字符(或它之前的某个字符)必然不同于另一个输入字符串中相同偏移量的字符。

对于两个数字序列,计算前导零的数量(可以为零)。如果计数不同,则数字序列中具有更多前导零的字符串被认为小于另一个字符串。

如果两个数字序列没有前导零,则将它们作为整数进行比较,即认为具有较长数字序列的字符串较大,如果两个序列长度相等,则按字典顺序进行比较。

如果两个数字序列都以零开头并且具有相同数量的前导零,则如果它们的长度相同,则按字典顺序对它们进行比较。如果长度不同,则较短的序列在其输入字符串中使用以下字符进行扩展,而另一个序列将被截断为相同的长度,并按字典顺序比较两个序列(类似于上面的非数字序列情况)。

前导零和打破平局的扩展字符(实际上跨非数字/数字序列边界传播)的处理不同于其他版本比较算法。

strverscmp ("no digit", "no digit")⇒ 0    /* same behavior as strcmp. */
strverscmp ("item#99", "item#100")⇒ <0   /* same prefix, but 99 < 100. */
strverscmp ("alpha1", "alpha001")⇒ >0   /* different number of leading zeros (0 and 2). */
strverscmp ("part1_f012", "part1_f01")⇒ >0   /* lexicographical comparison with leading zeros. */
strverscmp ("foo.009", "foo.0")⇒ <0   /* different number of leading zeros (2 and 1). */

strverscmp 是一个 GNU 扩展。

函数:int bcmp (const void *a1, const void *a2, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这是 memcmp 的过时别名,源自 BSD。

2.8. 整理函数

Collation Functions

在某些语言环境中,字典顺序的约定不同于字符代码的严格数字顺序。例如,在西班牙语中,出于校对目的,大多数带有变音符号(如重音)的字形不被视为不同的字母。另一方面,在捷克语中,两个字符的序列“ch”被视为在“h”和“i”之间整理的单个字母。

您可以使用函数 strcoll 和 strxfrm(在头文件 string.h 中声明)以及 wcscoll 和 wcsxfrm(在头文件 wchar 中声明)使用适合当前语言环境的排序规则来比较字符串。特别是这些函数使用的语言环境可以通过设置 LC_COLLATE 类别的语言环境来指定;请参阅语言环境和国际化。

在标准 C 语言环境中,strcoll 的排序顺序与 strcmp 的排序顺序相同。同样,在这种情况下,wcscoll 和 wcscmp 也是一样的。

实际上,这些函数的工作方式是通过应用映射将多字节字符串中的字符转换为表示字符串在当前语言环境的整理序列中的位置的字节序列。以简单的方式比较两个这样的字节序列相当于将字符串与语言环境的整理序列进行比较。

函数 strcoll 和 wcscoll 隐式执行此转换,以便进行一次比较。相比之下,strxfrm 和 wcsxfrm 显式地执行映射。如果您使用相同的字符串或字符串集进行多重比较,使用 strxfrm 或 wcsxfrm 只转换所有字符串一次,然后将转换后的字符串与 strcmp 或 wcscmp 进行比较可能会更有效。

函数:int strcoll (const char *s1, const char *s2)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

strcoll 函数类似于 strcmp,但使用当前语言环境的排序顺序进行排序(LC_COLLATE 语言环境)。参数是多字节字符串。

函数:int wcscoll (const wchar_t *ws1, const wchar_t *ws2)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

wcscoll 函数与 wcscmp 类似,但使用当前语言环境的排序序列进行排序(LC_COLLATE 语言环境)。

这是一个对字符串数组进行排序的示例,使用 strcoll 对它们进行比较。实际的排序算法这里就不写了;它来自 qsort(请参阅数组排序函数)。这里显示的代码的作用是说明如何在对字符串进行排序时比较它们。(在本节后面,我们将展示一种使用 strxfrm 更有效地做到这一点的方法。)

/* This is the comparison function used with qsort. */int
compare_elements (const void *v1, const void *v2)
{char * const *p1 = v1;char * const *p2 = v2;return strcoll (*p1, *p2);
}/* This is the entry point—the function to sortstrings using the locale’s collating sequence. */void
sort_strings (char **array, int nstrings)
{/* Sort temp_array by comparing the strings. */qsort (array, nstrings,sizeof (char *), compare_elements);
}

函数:size_t strxfrm (char *restrict to, const char *restrict from, size_t size)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

函数 strxfrm 使用由当前选择进行排序的语言环境确定的排序转换来转换多字节字符串,并将转换后的字符串存储在数组中。最多存储 size 个字节(包括终止的空字节)。

如果往返的字符串重叠,则行为未定义;请参阅复制字符串和数组。

返回值是整个转换字符串的长度。该值不受 size 值的影响,但如果大于或等于 size,则表示转换后的字符串没有完全适合数组 to。在这种情况下,只存储实际适合的字符串。要获取整个转换后的字符串,请使用更大的输出数组再次调用 strxfrm。

转换后的字符串可能比原始字符串长,也可能更短。

如果大小为零,则不存储任何字节。在这种情况下,strxfrm 只返回转换后的字符串长度的字节数。这对于确定分配的数组的大小很有用。如果大小为零,则无关紧要; to 甚至可能是一个空指针。

函数:size_t wcsxfrm (wchar_t *restrict wto, const wchar_t *wfrom, size_t size)

Preliminary: | MT-Safe locale | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

函数 wcsxfrm 使用由当前为排序规则选择的语言环境确定的排序规则转换来转换宽字符串 wfrom,并将转换后的字符串存储在数组 wto 中。最多存储 size 个宽字符(包括终止的空宽字符)。

如果字符串 wto 和 wfrom 重叠,则行为未定义;请参阅复制字符串和数组。

返回值是整个转换后的宽字符串的长度。这个值不受size的值影响,但如果大于等于size,则表示转换后的宽字符串没有完全适合数组wto。在这种情况下,只存储了实际适合的宽字符串。要获得整个转换后的宽字符串,请使用更大的输出数组再次调用 wcsxfrm。

转换后的宽字符串可能比原来的宽字符串长,也可能更短。

如果 size 为零,则不会将宽字符存储到 to。在这种情况下,wcsxfrm 只返回宽字符数,即转换后的宽字符串的长度。这对于确定分配的数组的大小很有用(记得乘以 sizeof (wchar_t))。如果 size 为零,wto 是什么并不重要; wto 甚至可能是一个空指针。

下面是一个示例,说明如何在计划进行多次比较时使用 strxfrm。它和前面的例子做同样的事情,但要快得多,因为它只需要转换每个字符串一次,不管它与其他字符串比较多少次。即使是分配和释放存储所需的时间也比我们节省的时间要少得多,因为有很多字符串。

struct sorter { char *input; char *transformed; };/* This is the comparison function used with qsortto sort an array of struct sorter. */int
compare_elements (const void *v1, const void *v2)
{const struct sorter *p1 = v1;const struct sorter *p2 = v2;return strcmp (p1->transformed, p2->transformed);
}/* This is the entry point—the function to sortstrings using the locale’s collating sequence. */void
sort_strings_fast (char **array, int nstrings)
{struct sorter temp_array[nstrings];int i;/* Set up temp_array.  Each element containsone input string and its transformed string. */for (i = 0; i < nstrings; i++){size_t length = strlen (array[i]) * 2;char *transformed;size_t transformed_length;temp_array[i].input = array[i];/* First try a buffer perhaps big enough.  */transformed = (char *) xmalloc (length);/* Transform array[i].  */transformed_length = strxfrm (transformed, array[i], length);/* If the buffer was not large enough, resize itand try again.  */if (transformed_length >= length){/* Allocate the needed space. +1 for terminating'\0' byte.  */transformed = xrealloc (transformed,transformed_length + 1);/* The return value is not interesting because we knowhow long the transformed string is.  */(void) strxfrm (transformed, array[i],transformed_length + 1);}temp_array[i].transformed = transformed;}/* Sort temp_array by comparing transformed strings. */qsort (temp_array, nstrings,sizeof (struct sorter), compare_elements);/* Put the elements back in the permanent arrayin their sorted order. */for (i = 0; i < nstrings; i++)array[i] = temp_array[i].input;/* Free the strings we allocated. */for (i = 0; i < nstrings; i++)free (temp_array[i].transformed);
}

宽字符版本的代码中有趣的部分如下所示:

void
sort_strings_fast (wchar_t **array, int nstrings)
{…/* Transform array[i].  */transformed_length = wcsxfrm (transformed, array[i], length);/* If the buffer was not large enough, resize itand try again.  */if (transformed_length >= length){/* Allocate the needed space. +1 for terminatingL'\0' wide character.  */transformed = xreallocarray (transformed,transformed_length + 1,sizeof *transformed);/* The return value is not interesting because we knowhow long the transformed string is.  */(void) wcsxfrm (transformed, array[i],transformed_length + 1);}…

请注意在 realloc 调用中与 sizeof (wchar_t) 的附加乘法。

兼容性说明:字符串归类函数是 ISO C90 的新特性。较旧的 C 方言没有等效的功能。ISO C90 修正案 1 中引入了宽字符版本。

2.9. 搜索函数

Search Functions

本节介绍对字符串和数组执行各种搜索操作的库函数。这些函数在头文件 string.h 中声明。

函数:void * memchr (const void *block, int c, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数在从块开始的对象的初始大小字节中查找字节 c(转换为无符号字符)的第一次出现。返回值是一个指向定位字节的指针,如果没有找到匹配,则返回一个空指针。

函数:wchar_t * wmemchr (const wchar_t *block, wchar_t wc, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数查找从块开始的对象的初始大小宽字符中第一次出现的宽字符 wc。返回值是指向所定位的宽字符的指针,如果没有找到匹配项,则返回空指针。

函数:void * rawmemchr (const void *block, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

通常使用 memchr 函数时知道字节 c 在参数指定的内存块中可用。但这意味着实际上并不需要 size 参数,并且不需要在运行时使用它执行的测试(检查是否到达块的末尾)。

rawmemchr 函数仅适用于这种异常频繁的情况。界面与 memchr 类似,只是缺少 size 参数。如果程序员在假设字节 c 存在于块中时出错,该函数将查看块指向的块的末尾之外。在这种情况下,结果是未指定的。否则返回值是指向定位字节的指针。

在查找字符串的结尾时,此函数特别有用。由于所有字符串都以空字节终止,因此调用如下

   rawmemchr (str, '\0')

永远不会超出字符串的末尾。

这个函数是一个 GNU 扩展。

函数:void * memrchr (const void *block, int c, size_t size)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 memrchr 与 memchr 类似,不同之处在于它从由块和大小定义的块的末尾向后搜索(而不是从前面向前搜索)。

这个函数是一个 GNU 扩展。

函数:char * strchr (const char *string, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strchr 函数查找从 string 开始的字符串中第一次出现的字节 c(转换为 char)。返回值是一个指向定位字节的指针,如果没有找到匹配,则返回一个空指针。

例如,

strchr ("hello, world", 'l')⇒ "llo, world"
strchr ("hello, world", '?')⇒ NULL

终止的空字节被认为是字符串的一部分,因此您可以使用此函数通过将零指定为 c 参数的值来获取指向字符串结尾的指针。

当 strchr 返回一个空指针时,它不会让您知道它找到的终止空字节的位置。如果您需要该信息,使用 strchrnul 比再次搜索它更好(但便携性较差)。

函数:wchar_t * wcschr (const wchar_t *wstring, wchar_t wc)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcschr 函数查找从 wstring 开始的宽字符串中第一次出现的宽字符 wc。返回值是指向所定位的宽字符的指针,如果没有找到匹配项,则返回空指针。

终止的空宽字符被认为是宽字符串的一部分,因此您可以使用此函数通过将空宽字符指定为 wc 参数的值来获取指向宽字符串结尾的指针。不过,在这种情况下使用 wcschrnul 会更好(但便携性较差)。

函数:char * strchrnul (const char *string, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strchrnul 与 strchr 相同,只是如果找不到字节,则返回指向字符串终止空字节的指针,而不是空指针。

这个函数是一个 GNU 扩展。

函数:wchar_t * wcschrnul (const wchar_t *wstring, wchar_t wc)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcschrnul 与 wcschr 相同,只是如果它没有找到宽字符,则返回指向宽字符串终止空宽字符的指针,而不是空指针。

这个函数是一个 GNU 扩展。

strchr 函数的一种有用但不寻常的用法是当人们想要一个指向终止字符串的空字节的指针时。这通常是这样写的:

  s += strlen (s);

这几乎是最优的,但是加法运算重复了一些已经在 strlen 函数中完成的工作。一个更好的解决方案是:

  s = strchr (s, '\0');

strchr 的第二个参数没有限制,所以它也可以为零。那些深思熟虑的读者现在可能会指出 strchr 函数比 strlen 函数更昂贵,因为我们有两个中止标准。这是对的。但是在 GNU C 库中, strchr 的实现以一种特殊的方式进行了优化,因此 strchr 实际上更快。

函数:char * strrchr (const char *string, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 strrchr 与 strchr 类似,不同之处在于它从字符串字符串的末尾向后搜索(而不是从前面向前搜索)。

例如,

strrchr ("hello, world", 'l')⇒ "ld"

函数:wchar_t * wcsrchr (const wchar_t *wstring, wchar_t wc)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 wcsrchr 与 wcschr 类似,不同之处在于它从字符串 wstring 的末尾向后搜索(而不是从前面向前搜索)。

函数:char * strstr (const char *haystack, const char *needle)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这与 strchr 类似,不同之处在于它在 haystack 中搜索子字符串 needle 而不仅仅是单个字节。它返回一个指向字符串 haystack 的指针,该指针是子字符串的第一个字节,如果没有找到匹配项,则返回一个空指针。如果 needle 为空字符串,则该函数返回 haystack。

例如,

strstr ("hello, world", "l")⇒ "llo, world"
strstr ("hello, world", "wo")⇒ "world"

函数:wchar_t * wcsstr (const wchar_t *haystack, const wchar_t *needle)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这与 wcschr 类似,不同之处在于它在 haystack 中搜索子字符串 needle 而不仅仅是单个宽字符。它返回一个指向字符串 haystack 的指针,该指针是子字符串的第一个宽字符,如果没有找到匹配项,则返回一个空指针。如果 needle 为空字符串,则该函数返回 haystack。

函数:wchar_t * wcswcs (const wchar_t *haystack, const wchar_t *needle)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcswcs 是 wcsstr 的已弃用别名。这是在 ISO C90 修订 1 发布之前 X/Open 可移植性指南中最初使用的名称。

函数:char * strcasestr (const char *haystack, const char *needle)

Preliminary: | MT-Safe locale | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这类似于 strstr,只是它在搜索子字符串时忽略大小写。与 strcasecmp 一样,它取决于区域设置,大写和小写字符如何相关,并且参数是多字节字符串。

例如,

strcasestr ("hello, world", "L")⇒ "llo, world"
strcasestr ("hello, World", "wo")⇒ "World"

函数:void * memmem (const void *haystack, size_t haystack-len, const void *needle, size_t needle-len)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这就像 strstr,但 needle 和 haystack 是字节数组而不是字符串。needle-len 是 needle 的长度,而 haystack-len 是 haystack 的长度。

这个函数是一个 GNU 扩展。

函数:size_t strspn (const char *string, const char *skipset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strspn (“string span”) 函数返回字符串的初始子字符串的长度,该字符串完全由作为字符串跳跃集指定的集合成员的字节组成。跳过集中字节的顺序并不重要。

例如,

strspn ("hello, world", "abcdefghijklmnopqrstuvwxyz")⇒ 5

在多字节字符串中,由多于一个字节组成的字符不被视为单个实体。每个字节单独处理。该函数不依赖于语言环境。

函数:size_t wcsspn (const wchar_t *wstring, const wchar_t *skipset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcsspn (“wide character string span”) 函数返回 wstring 的初始子字符串的长度,该子字符串完全由作为字符串 skipset 指定的集合的成员的宽字符组成。skipset 中宽字符的顺序并不重要。

函数:size_t strcspn (const char *string, const char *stopset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strcspn (“string complement span”) 函数返回字符串的初始子字符串的长度,该字符串完全由不属于字符串停止集指定的集合的字节组成。(换句话说,它返回作为 set stopset 成员的 string 中第一个字节的偏移量。)

例如,

strcspn ("hello, world", " \t\n,.;!?")⇒ 5

在多字节字符串中,包含多个字节的字符不被视为单个实体。每个字节单独处理。该函数不依赖于语言环境。

函数:size_t wcscspn (const wchar_t *wstring, const wchar_t *stopset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcscspn (“wide character string complement span”) 函数返回 wstring 的初始子字符串的长度,该子字符串完全由不属于字符串停止集指定的集合的成员的宽字符组成。(换句话说,它返回 string 中第一个宽字符的偏移量,它是 set stopset 的成员。)

函数:char * strpbrk (const char *string, const char *stopset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strpbrk (“string pointer break”) 函数与 strcspn 相关,不同之处在于它返回指向字符串中第一个字节的指针,该字节是集合停止集的成员,而不是初始子字符串的长度。如果在 stopset 中没有找到这样的字节,它将返回一个空指针。

例如,

strpbrk ("hello, world", " \t\n,.;!?")⇒ ", world"

在多字节字符串中,由多于一个字节组成的字符不被视为单个实体。每个字节单独处理。该函数不依赖于语言环境。

函数:wchar_t * wcspbrk (const wchar_t *wstring, const wchar_t *stopset)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

wcspbrk (“wide character string pointer break”) 函数与 wcscspn 相关,除了它返回指向 wstring 中第一个宽字符的指针,该指针是集合停止集的成员,而不是初始子字符串的长度。如果在 stopset 中没有找到这样的宽字符,则返回一个空指针。

2.9.1. 兼容性字符串搜索函数

Compatibility String Search Functions

函数:char * index (const char *string, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

index 是 strchr 的另一个名称;它们完全相同。新代码应始终使用 strchr,因为该名称是在 ISO C 中定义的,而 index 是 BSD 发明,从未在 System V 派生系统上可用。

函数:char * rindex (const char *string, int c)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

rindex 是 strrcr 的另一个名称;它们完全相同。新代码应该始终使用 strrchr,因为这个名称是在 ISO C 中定义的,而 rindex 是一项 BSD 发明,它从未在 System V 派生系统上可用。

2.10. 在字符串中查找标记

Finding Tokens in a String

程序需要进行一些简单的词法分析和解析是相当普遍的,例如将命令字符串拆分为标记。您可以使用在头文件 string.h 中声明的 strtok 函数来执行此操作。

函数:char * strtok (char *restrict newstring, const char *restrict delimiters)

Preliminary: | MT-Unsafe race:strtok | AS-Unsafe | AC-Safe | See POSIX Safety Concepts.

通过对函数 strtok 进行一系列调用,可以将字符串拆分为标记。

要拆分的字符串仅在第一次调用时作为 newstring 参数传递。strtok 函数使用它来设置一些内部状态信息。通过将空指针作为 newstring 参数传递来指示从同一字符串中获取其他标记的后续调用。使用另一个非空 newstring 参数调用 strtok 会重新初始化状态信息。保证没有其他库函数会在你背后调用 strtok(这会弄乱这个内部状态信息)。

delimiters 参数是一个字符串,它指定一组分隔符,这些分隔符可以围绕正在提取的标记。作为该集合成员的所有初始字节都将被丢弃。不属于这组分隔符的第一个字节标志着下一个标记的开始。通过查找作为分隔符集成员的下一个字节来找到标记的结尾。原始字符串 newstring 中的这个字节被空字节覆盖,并返回指向 newstring 中标记开头的指针。

在下一次调用 strtok 时,搜索从标记前一个令牌结束的字节之外的下一个字节开始。请注意,在对 strtok 的一系列调用中的每个调用中,定界符的定界符集不必相同。

如果到达字符串 newstring 的末尾,或者如果字符串的其余部分仅包含分隔符字节,则 strtok 返回一个空指针。

在多字节字符串中,由多于一个字节组成的字符不被视为单个实体。每个字节单独处理。该函数不依赖于语言环境。

函数:wchar_t * wcstok (wchar_t *newstring, const wchar_t *delimiters, wchar_t **save_ptr)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

通过对函数 wcstok 进行一系列调用,可以将字符串拆分为标记。

要拆分的字符串仅在第一次调用时作为 newstring 参数传递。wcstok 函数使用它来设置一些内部状态信息。通过将空指针作为 newstring 参数传递来指示从同一宽字符串中获取其他标记的后续调用,这会导致使用先前存储在 save_ptr 中的指针。

delimiters 参数是一个宽字符串,它指定一组分隔符,这些分隔符可以围绕正在提取的标记。作为该集合成员的所有初始宽字符都将被丢弃。不属于这组分隔符的第一个宽字符标志着下一个标记的开始。通过查找作为分隔符集成员的下一个宽字符来找到标记的结尾。原始宽字符串 newstring 中的这个宽字符被一个空宽字符覆盖,经过覆盖的宽字符的指针保存在 save_ptr 中,并返回指向 newstring 中标记开头的指针。

在下一次调用 wcstok 时,搜索从标记前一个标记结束的字符之外的下一个宽字符开始。请注意,在对 wcstok 的一系列调用中,定界符的集合不必每次调用都相同。

如果到达宽字符串 newstring 的末尾,或者如果字符串的其余部分仅包含分隔符宽字符,则 wcstok 返回一个空指针。

警告:由于 strtok 和 wcstok 更改了它们正在解析的字符串,因此在使用 strtok/wcstok 解析之前,您应该始终将字符串复制到临时缓冲区(请参阅复制字符串和数组)。如果您允许 strtok 或 wcstok 修改来自程序另一部分的字符串,您就是在自找麻烦。在 strtok 或 wcstok 对其进行修改后,该字符串可能会用于其他目的,并且它不会具有预期值。

您正在操作的字符串甚至可能是一个常量。然后当 strtok 或 wcstok 尝试修改它时,您的程序将收到一个致命信号,用于写入只读内存。请参阅程序错误信号。即使 strtok 或 wcstok 的操作不需要修改字符串(例如,如果只有一个标记),字符串也可以(并且在 GNU C 库的情况下)会被修改。

这是一般原则的一个特例:如果程序的一部分不以修改某个数据结构为目的,那么临时修改该数据结构是容易出错的。

函数 strtok 是不可重入的,而 wcstok 是。请参阅信号处理和不可重入函数,了解重入在何处以及为何重要的讨论。

这是一个简单的例子,展示了 strtok 的使用。

#include <string.h>
#include <stddef.h>…const char string[] = "words separated by spaces -- and, punctuation!";
const char delimiters[] = " .,;:!-";
char *token, *cp;…cp = strdupa (string);                /* Make writable copy.  */
token = strtok (cp, delimiters);      /* token => "words" */
token = strtok (NULL, delimiters);    /* token => "separated" */
token = strtok (NULL, delimiters);    /* token => "by" */
token = strtok (NULL, delimiters);    /* token => "spaces" */
token = strtok (NULL, delimiters);    /* token => "and" */
token = strtok (NULL, delimiters);    /* token => "punctuation" */
token = strtok (NULL, delimiters);    /* token => NULL */

GNU C 库包含另外两个用于标记字符串的函数,它们克服了不可重入性的限制。它们不适用于宽字符串。

函数:char * strtok_r (char *newstring, const char *delimiters, char **save_ptr)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

就像 strtok 一样,此函数将字符串拆分为多个标记,这些标记可以通过连续调用 strtok_r 来访问。不同之处在于,与 wcstok 中一样,有关下一个标记的信息存储在第三个参数 save_ptr 指向的空间中,该参数是指向字符串指针的指针。使用 newstring 的空指针调用 strtok_r 并在调用之间保持 save_ptr 不变,可以在不妨碍重入的情况下完成这项工作。

此函数在 POSIX.1 中定义,可以在许多支持多线程的系统上找到。

函数:char * strsep (char **string_ptr, const char *delimiter)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

此函数具有与 strtok_r 类似的功能,其中 newstring 参数替换为 save_ptr 参数。移动指针的初始化必须由用户完成。对 strsep 的连续调用沿着由分隔符分隔的标记移动指针,返回下一个标记的地址并更新 string_ptr 以指向下一个标记的开头。

strsep 和 strtok_r 之间的一个区别是,如果输入字符串在一行中包含多个来自分隔符的字节,则 strsep 为来自分隔符的每一对字节返回一个空字符串。这意味着程序通常应该在处理它之前测试 strsep 返回一个空字符串。

这个函数是在 4.3BSD 中引入的,因此被广泛使用。

以下是使用 strsep 时上述示例的外观。

#include <string.h>
#include <stddef.h>…const char string[] = "words separated by spaces -- and, punctuation!";
const char delimiters[] = " .,;:!-";
char *running;
char *token;…running = strdupa (string);
token = strsep (&running, delimiters);    /* token => "words" */
token = strsep (&running, delimiters);    /* token => "separated" */
token = strsep (&running, delimiters);    /* token => "by" */
token = strsep (&running, delimiters);    /* token => "spaces" */
token = strsep (&running, delimiters);    /* token => "" */
token = strsep (&running, delimiters);    /* token => "" */
token = strsep (&running, delimiters);    /* token => "" */
token = strsep (&running, delimiters);    /* token => "and" */
token = strsep (&running, delimiters);    /* token => "" */
token = strsep (&running, delimiters);    /* token => "punctuation" */
token = strsep (&running, delimiters);    /* token => "" */
token = strsep (&running, delimiters);    /* token => NULL */

函数:char * basename (const char *filename)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

basename 函数的 GNU 版本返回文件名中路径的最后一个组成部分。此函数是首选用法,因为它不修改参数、文件名,并尊重尾部斜杠。basename 的原型可以在 string.h 中找到。请注意,如果包含 libgen.h,此函数将被 XPG 版本覆盖。

使用 GNU 基本名称的示例:

#include <string.h>int
main (int argc, char *argv[])
{char *prog = basename (argv[0]);if (argc < 2){fprintf (stderr, "Usage %s <arg>\n", prog);exit (1);}…
}

可移植性说明:此功能在不同的系统上可能会产生不同的结果。

函数:char * basename (char *path)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

这是标准 XPG 定义的基本名称。它在本质上与 GNU 版本相似,但可以通过删除尾随 ‘/’ 字节来修改路径。如果路径完全由 ‘/’ 字节组成,则将返回“/”。此外,如果 path 为 NULL 或空字符串,则为 “.” 被退回。XPG 版本的原型可以在 libgen.h 中找到。

使用 XPG 基本名称的示例:

#include <libgen.h>int
main (int argc, char *argv[])
{char *prog;char *path = strdupa (argv[0]);prog = basename (path);if (argc < 2){fprintf (stderr, "Usage %s <arg>\n", prog);exit (1);}…}

函数:char * dirname (char *path)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

dirname 函数是对XPG 版本的basename 的补充。它返回由 path 指定的文件的父目录。如果 path 为 NULL、空字符串或不包含 ‘/’ 字节,则 “.” 被退回。这个函数的原型可以在 libgen.h 中找到。

2.11. 擦除敏感数据

Erasing Sensitive Data

敏感数据,如加密密钥,应在使用后从内存中删除,以降低错误将其暴露给外界的风险。但是,编译器优化可能会确定擦除操作是“不必要的”,并将其从生成的代码中删除,因为在释放后没有正确的程序可以访问包含敏感数据的变量或堆对象。由于擦除是针对错误的预防措施,因此这种优化是不合适的。

函数explicit_bzero 擦除一块内存,并保证编译器不会将擦除视为“不必要的”。

#include <string.h>extern void encrypt (const char *key, const char *in,char *out, size_t n);
extern void genkey (const char *phrase, char *key);void encrypt_with_phrase (const char *phrase, const char *in,char *out, size_t n)
{char key[16];genkey (phrase, key);encrypt (key, in, out, n);explicit_bzero (key, 16);
}

在此示例中,如果使用了 memset、bzero 或手写循环,编译器可能会将它们删除为“不必要的”。

警告:explicit_bzero 不保证敏感数据会从计算机内存中完全擦除。临时存储区域中可能存在副本,例如寄存器和“临时”堆栈空间;由于这些对源代码不可见,因此库函数无法擦除它们。

此外,explicit_bzero 仅在 RAM 上运行。如果敏感数据对象除了调用explicit_bzero 之外不需要获取其地址,它可能会完全存储在CPU 寄存器中,直到调用explicit_bzero。然后将其复制到 RAM 中,副本将被擦除,而原始将保持不变。RAM 中的数据比寄存器中的数据更容易被错误暴露,因此这会创建一个短暂的窗口,在该窗口中,与程序根本不尝试擦除数据相比,数据暴露的风险更大。

将敏感变量声明为 volatile 会使上述两个问题变得更糟;一个 volatile 变量将在其整个生命周期中存储在内存中,并且编译器将对其进行比其他情况下更多的副本。尝试通过 volatile 限定的指针“手动”擦除普通变量根本行不通——因为变量本身不是 volatile,一些编译器会忽略指针上的限定并删除擦除。

说了这么多,在大多数情况下,使用 explicit_bzero 总比不使用要好。目前,唯一能做得更彻底的方法就是用汇编语言编写整个敏感操作。我们预计未来的编译器将识别对explicit_bzero 的调用,并采取适当的措施来擦除受影响数据的所有副本,无论它们在哪里。

函数:void explicit_bzero (void *block, size_t len)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

与 bzero 一样,explicit_bzero 将零写入从 block 开始的 len 个内存字节。零总是被写入,即使编译器可以确定这是“不必要的”,因为没有正确的程序可以将它们读回。

注意:explicit_bzero 禁用的唯一优化是删除对内存的“不必要”写入。编译器可以为调用 memset 执行所有其他优化。例如,它可以用内联内存写入替换函数调用,并且它可以假设块不能是空指针。

可移植性说明:该功能最早出现在 OpenBSD 5.5 中,尚未标准化。其他系统可能以不同的名称提供相同的功能,例如explicit_memset、memset_s 或SecureZeroMemory。

GNU C 库在 string.h 中声明了这个函数,但在其他系统上它可能在 strings.h 中。

2.12. 洗牌字节

Shuffling Bytes

下面的函数解决了长期存在的编程难题:“我如何以字符串形式获取好的数据并轻松地将其变成垃圾?”为自己编写代码并不是一件难事,但 GNU C 库的作者希望使其尽可能方便。

要擦除数据,请使用 explicit_bzero(请参阅擦除敏感数据);要对其进行可逆混淆,请使用 memfrob(请参阅混淆数据)。

函数:char * strfry (char *string)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

strfry 对字符串执行就地洗牌。每个字符被交换到随机选择的位置,在字符串的从字符的原始位置开始的部分内。(这是用于无偏洗牌的 Fisher-Yates 算法。)

调用 strfry 不会干扰任何具有全局状态的随机数生成器(请参阅伪随机数)。

strfry 的返回值始终是字符串。

可移植性说明:这个函数是 GNU C 库独有的。它在 string.h 中声明。

2.13. 混淆数据

Obfuscating Data

memfrob 函数可逆地混淆二进制数据数组。这不是真正的加密;混淆后的数据与原始数据仍然具有明确的关系,无需密钥即可解除混淆。它类似于 Usenet 上用于掩盖冒犯性笑话、小说作品的剧透等的“Rot13”密码,但它可以应用于任意二进制数据。

需要真正加密的程序——一种完全掩盖原始数据并且在不知道密钥的情况下无法反转的转换——应该使用专用的密码库,例如 libgcrypt。

需要销毁数据的程序应该使用explicit_bzero(参见Erasing Sensitive Data),或者可能使用strfry(参见Shuffle Bytes)。

函数:void * memfrob (void *mem, size_t length)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

函数 memfrob 对从 mem 开始的长度字节数据进行模糊处理。每个字节与二进制模式 00101010(十六进制 0x2A)按位异或。返回值始终是 mem。

memfrob 对同一数据第二次返回其原始状态。

可移植性 注意:这个函数是 GNU C 库独有的。它在 string.h 中声明。

2.14. 编码二进制数据

Encode Binary Data

要在仅支持文本的环境中存储或传输二进制数据,必须通过将输入字节映射到允许存储或传输范围内的字节来对二进制数据进行编码。SVID 系统(以及现在的 XPG 兼容系统)为这项任务提供了最少的支持。

函数:char * l64a (long int n)

Preliminary: | MT-Unsafe race:l64a | AS-Unsafe | AC-Safe | See POSIX Safety Concepts.

此函数使用基本字符集中的字节对 32 位输入值进行编码。它返回一个指向 7 字节缓冲区的指针,该缓冲区包含 n 的编码版本。要对一系列字节进行编码,用户必须将返回的字符串复制到目标缓冲区。如果 n 为零,它返回空字符串,这有点奇怪,但标准规定。

警告:由于使用了静态缓冲区,因此不应在多线程程序中使用此函数。在 C 库中,此函数没有线程安全的替代方案。

兼容性说明:XPG 标准规定如果 n 为负数,则 l64a 的返回值未定义。在 GNU 实现中,l64a 将其参数视为无符号,因此它将返回任何非零 n 的合理编码;但是,可移植程序不应该依赖于此。

要对大缓冲区进行编码,l64a 必须在循环中调用,缓冲区的每个 32 位字一次。例如,可以执行以下操作:

char *
encode (const void *buf, size_t len)
{/* We know in advance how long the buffer has to be. */unsigned char *in = (unsigned char *) buf;char *out = malloc (6 + ((len + 3) / 4) * 6 + 1);char *cp = out, *p;/* Encode the length. *//* Using ‘htonl’ is necessary so that the data can bedecoded even on machines with different byte order.‘l64a’ can return a string shorter than 6 bytes, sowe pad it with encoding of 0 ('.') at the end byhand. */p = stpcpy (cp, l64a (htonl (len)));cp = mempcpy (p, "......", 6 - (p - cp));while (len > 3){unsigned long int n = *in++;n = (n << 8) | *in++;n = (n << 8) | *in++;n = (n << 8) | *in++;len -= 4;p = stpcpy (cp, l64a (htonl (n)));cp = mempcpy (p, "......", 6 - (p - cp));}if (len > 0){unsigned long int n = *in++;if (--len > 0){n = (n << 8) | *in++;if (--len > 0)n = (n << 8) | *in;}cp = stpcpy (cp, l64a (htonl (n)));}*cp = '\0';return out;
}

奇怪的是,该库没有提供所需的完整功能,但就这样吧。

要解码使用 l64a 生成的数据,应使用以下函数。

函数:long int a64l (const char *string)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

参数字符串应该包含一个由调用 l64a 生成的字符串。该函数至少处理此字符串的 6 个字节,并根据下表对找到的字节进行解码。当它发现一个不在表中的字节时,它会停止解码,就像 atoi 一样; 如果你有一个被分成几行的缓冲区,你必须小心跳过行尾字节。

解码后的数字作为 long int 值返回。

l64a 和 a64l 函数使用 base 64 编码,其中编码字符串的每个字节代表输入字的 6 位。这些符号用于基数 64 位:

    0    1   2   3   4   5   6   7
0   .   /   0   1   2   3   4   5
8   6   7   8   9   A   B   C   D
16  E   F   G   H   I   J   K   L
24  M   N   O   P   Q   R   S   T
32  U   V   W   X   Y   Z   a   b
40  c   d   e   f   g   h   i   j
48  k   l   m   n   o   p   q   r
56  s   t   u   v   w   x   y   z

这种编码方案不是标准的。还有一些其他使用更广泛的编码方法(UU 编码、MIME 编码)。通常,最好使用其中一种编码。

2.15. Argz 和 Envz 向量

Argz and Envz Vectors

argz 向量是连续内存块中的字符串向量,每个元素与其相邻元素之间用空字节 (‘\0’) 分隔。

Envz 向量是 argz 向量的扩展,其中每个元素都是一个名称-值对,由一个 ‘=’ 字节分隔(如在 Unix 环境中)。

2.15.1. Argz 函数

Argz Functions

每个 argz 向量由指向第一个元素的指针表示,类型为 char * 和大小,类型为 size_t,两者都可以初始化为 0 以表示空的 argz 向量。所有 argz 函数都接受一个指针和一个大小参数,或者指向它们的指针,如果它们将被修改的话。

argz 函数使用 malloc/realloc 来分配/增长 argz 向量,因此使用这些函数创建的任何 argz 向量都可以使用 free 释放;相反,任何可能增长字符串的 argz 函数都期望该字符串已使用 malloc 分配(那些仅检查其参数或修改它们的 argz 函数将在任何类型的内存上工作)。请参阅无约束分配。

所有进行内存分配的 argz 函数都有一个返回类型 error_t,如果成功则返回 0,如果发生分配错误则返回 ENOMEM。

这些函数在标准包含文件 argz.h 中声明。

函数:error_t argz_create (char *const argv[], char **argz, size_t *argz_len)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_create 函数将 Unix 风格的参数向量 argv(指向普通 C 字符串的指针向量,以 (char *)0 终止;参见程序参数)转换为具有相同元素的 argz 向量,该向量在 argz 和 argz_len 中返回。

函数:error_t argz_create_sep (const char *string, int sep, char **argz, size_t *argz_len)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_create_sep 函数将字符串字符串转换为 argz 向量(在 argz 和 argz_len 中返回),方法是在每次出现字节 sep 时将其拆分为元素。

函数:size_t argz_count (const char *argz, size_t argz_len)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

返回 argz 向量 argz 和 argz_len 中的元素数。

函数:void argz_extract (const char *argz, size_t argz_len, char **argv)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

argz_extract 函数将 argz 向量 argz 和 argz_len 转换为存储在 argv 中的 Unix 样式参数向量,方法是将指向 argz 中每个元素的指针放入 argv 中的连续位置,后跟 0 终止符。必须为 argv 预先分配足够的保存 argz 中所有元素的空间加上终止 (char *)0 ((argz_count (argz, argz_len) + 1) * sizeof (char *) 字节应该足够了)。请注意,存储在 argv 中的字符串指针指向 argz(它们不是副本),因此如果在 argv 仍处于活动状态时要更改它,则必须复制 argz。此函数可用于将 argz 中的元素传递给 exec 函数(请参阅执行文件)。

函数:void argz_stringify (char *argz, size_t len, int sep)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

argz_stringify 将 argz 转换为普通字符串,其中元素由字节 sep 分隔,方法是将 argz 中的每个 ‘\0’(除了最后一个,它终止字符串)替换为 sep。这对于以可读方式打印 argz 非常方便。

函数:error_t argz_add (char **argz, size_t *argz_len, const char *str)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_add 函数将字符串 str 添加到 argz 向量 *argz 的末尾,并相应地更新 *argz 和 *argz_len。

函数:error_t argz_add_sep (char **argz, size_t *argz_len, const char *str, int delim)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_add_sep 函数与 argz_add 类似,但 str 在出现字节分隔符时被拆分为结果中的单独元素。例如,这对于将 Unix 搜索路径的组件添加到 argz 向量很有用,方法是使用 ‘:’ 作为 delim 的值。

函数:error_t argz_append (char **argz, size_t *argz_len, const char *buf, size_t buf_len)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_append 函数将从 buf 开始的 buf_len 字节附加到 argz 向量 *argz,重新分配 *argz 以容纳它,并将 buf_len 添加到 *argz_len。

函数:void argz_delete (char **argz, size_t *argz_len, char *entry)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

如果 entry 指向 argz 向量 *argz 中元素之一的开头,则 argz_delete 函数将删除该条目并重新分配 *argz,相应地修改 *argz 和 *argz_len。请注意,由于破坏性 argz 函数通常会重新分配其 argz 参数,指向 argz 向量的指针(例如 entry)将变得无效。

函数:error_t argz_insert (char **argz, size_t *argz_len, char *before, const char *entry)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

argz_insert 函数将字符串条目插入到 argz 向量 *argz 中,就在 before 指向的现有元素之前,重新分配 *argz 并更新 *argz 和 *argz_len。如果 before 为 0,则将条目添加到末尾(就像通过 argz_add 一样)。由于第一个元素实际上与 *argz 相同,将 *argz 作为 before 的值传入将导致条目被插入到开头。

函数:char * argz_next (const char *argz, size_t argz_len, const char *entry)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

argz_next 函数提供了一种对 argz 向量 argz 中的元素进行迭代的便捷方式。它返回指向元素条目后 argz 中下一个元素的指针,如果条目后面没有元素,则返回 0。如果 entry 为 0,则返回 argz 的第一个元素。

这种行为暗示了两种迭代方式:

    char *entry = 0;while ((entry = argz_next (argz, argz_len, entry)))action;

(双括号是必要的,以使一些 C 编译器对他们认为有问题的 while-test 闭嘴)并且:

    char *entry;for (entry = argz;entry;entry = argz_next (argz, argz_len, entry))action;

请注意,后者取决于 argz 如果它是空的(而不是指向空内存块的指针),则它的值为 0;对于此处的函数创建的 argz 向量,维护此不变量。

函数:error_t argz_replace (char **argz, size_t *argz_len, const char *str, const char *with, unsigned *replace_count)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

将 argz 中出现的任何字符串 str 替换为 with,必要时重新分配 argz。如果 replace_count 不为零,*replace_count 将按执行的替换次数递增。

2.15.2. Emvz 函数

Envz Functions

Envz 向量只是 argz 向量,对每个元素的形式有额外的约束;因此,也可以在它们上使用 argz 函数,这是有意义的。

envz 向量中的每个元素都是一个名称-值对,由“=”字节分隔;如果一个元素中存在多个 ‘=’ 字节,则第一个之后的那些被视为值的一部分,并像所有其他非’\0’ 字节一样对待。

如果元素中不存在“=”字节,则该元素被视为“null”条目的名称,这与具有空值的条目不同:如果给定 null 条目的名称,envz_get 将返回 0,而条目空值将导致值“”;但是,envz_entry 仍会找到此类条目。可以使用 envz_strip 函数删除空条目。

与 argz 函数一样,可能分配内存(并因此失败)的 envz 函数的返回类型为 error_t,并返回 0 或 ENOMEM。

这些函数在标准包含文件 envz.h 中声明。

函数:char * envz_entry (const char *envz, size_t envz_len, const char *name)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

envz_entry 函数在 envz 中查找名称为 name 的条目,并返回指向整个条目的指针——即以 name 开头的 argz 元素,后跟一个 ‘=’ 字节。如果没有具有该名称的条目,则返回 0。

函数:char * envz_get (const char *envz, size_t envz_len, const char *name)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

envz_get 函数在 envz 中查找名称为 name 的条目(如 envz_entry),并返回指向该条目的值部分的指针(在“=”之后)。如果没有具有该名称的条目(或只有空条目),则返回 0。

函数:error_t envz_add (char **envz, size_t *envz_len, const char *name, const char *value)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

envz_add 函数向 *envz 添加一个条目(更新 *envz 和 *envz_len),其名称为 name 和 value 值。如果 envz 中已经存在同名条目,则首先将其删除。如果 value 为 0,则新条目将是特殊的 null 类型的条目(如上所述)。

函数:error_t envz_merge (char **envz, size_t *envz_len, const char *envz2, size_t envz2_len, int override)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

envz_merge 函数将 envz2 中的每个条目添加到 envz,就像使用 envz_add 一样,更新 *envz 和 *envz_len。如果 override 为真,则 envz2 中的值将取代 envz 中具有相同名称的值,否则不会。

在这方面,空条目与其他条目一样被处理,因此如果 override 为 false,则 envz 中的空条目可以防止将 envz2 中的同名条目添加到 envz。

函数:void envz_strip (char **envz, size_t *envz_len)

Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.

envz_strip 函数从 envz 中删除任何空条目,更新 *envz 和 *envz_len。

函数:void envz_remove (char **envz, size_t *envz_len, const char *name)

Preliminary: | MT-Safe | AS-Unsafe heap | AC-Unsafe mem | See POSIX Safety Concepts.

envz_remove 函数从 envz 中删除名为 name 的条目,更新 *envz 和 *envz_len。

3. 参考

  • String and Array Utilities

glibc 知:手册05:字符串和数组相关推荐

  1. glibc 知:手册12:输入/输出流

    文章目录 1. 前言 2. 输入/输出流 2.1. 流 2.2. 标准流 2.3. 打开流 2.4. 关闭流 2.5. 流和线程 2.6. 国际化应用程序中的流 2.7. 按字符或行的简单输出 2.8 ...

  2. 数组-05. 字符串字母大小写转换

    数组-05. 字符串字母大小写转换(10) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 张彤彧(浙江大学) 输入一个以#结束的字符 ...

  3. glibc 知:内容

    文章目录 1. 前言 2. 内容 2.1. Error Reporting 错误报告 2.2. Virtual Memory Allocation And Paging 虚拟内存分配和分页 2.3. ...

  4. php手册数组函数,PHP - Manual手册 - 函数参考 - Array 数组函数 - array_diff计算数组的差集...

    PHP - Manual手册 - 函数参考 - Array 数组函数 - array_diff计算数组的差集 array_diff (PHP 4 >= 4.0.1, PHP 5) array_d ...

  5. java中字符串和数组如何比较_[Java教程]javascript中数组和字符串的方法比较

    [Java教程]javascript中数组和字符串的方法比较 0 2016-07-19 23:00:05 ×目录[1]可索引 [2]转换 [3]拼接[4]创建[5]位置 前面的话 字符串和数组有很多的 ...

  6. php 字符串放到数组中,在PHP中将字符串转换为数组(Converting string into array in php)...

    在PHP中将字符串转换为数组(Converting string into array in php) 我有像下面的字符串 ["Day1"]["Morning" ...

  7. 数组和字符串的相互转换---------数组转换为字符串

    1.Array.join()方法 将数组的每一项用指定字符连接形成一个字符串.默认连接字符为 "," 逗号. 注:将字符串转化为数组的String.split("分隔符& ...

  8. iOS开发:沙盒机制以及利用沙盒存储字符串、数组、字典等数据

    iOS开发:沙盒机制以及利用沙盒存储字符串.数组.字典等数据 1.初识沙盒:(1).存储在内存中的数据,程序关闭,内存释放,数据就会丢失,这种数据是临时的.要想数据永久保存,将数据保存成文件,存储到程 ...

  9. OC基础第四讲--字符串、数组、字典、集合的常用方法

    OC基础第四讲--字符串.数组.字典.集合的常用方法 字符串.数组.字典.集合有可变和不可变之分.以字符串为例,不可变字符串本身值不能改变,必须要用相应类型来接收返回值:而可变字符串调用相应地方法后, ...

最新文章

  1. 【实战分享】漫谈 gRPC的选型
  2. 《犯罪心理学》读书笔记(part11)--犯罪心理的性别差异(中)
  3. 【图像超分辨率】Multi-scale Residual Network for Image Super-Resolution
  4. 什么是 PostgreSQL 横向子查询?
  5. Linux虚拟网络原理小结
  6. JXSE 2.5 : What's Cool #6 -- PeerGroup Executor and ScheduledExcutor
  7. scipy.special —— 排列、组合与阶乘
  8. element ui 邮箱非必填校验
  9. 51Nod-1259-整数划分 V2
  10. 写博文有助于提高编程能力,因为写文章比写代码难多了
  11. idea android模拟器无法启动,Flutter Hello world应用程序无法在Android模拟器x86_64上启动...
  12. 存储函数与存储过程的区别
  13. php smarty fetch,fetch - [ smarty完全中文手册 ] - 在线原生手册 - php中文网
  14. Linux创建.txt文件
  15. 毒鸡汤|心情不好的时候,看一看。你会发现心情会很不好。
  16. VTK Camera
  17. c语言临时内存变量释放,C语言中的内存分配与释放
  18. 郁闷湖上被宰的文章: 仰恩大学评估+废CET !
  19. ROS:rosdep init 安装问题解决方案-转载
  20. EXTJS 6 Grid 滚动到底部 触发事件(如:加载数据)

热门文章

  1. RabbitMQ(六)——Spring boot中消费消息的两种方式
  2. 聊聊支付通道那些事儿——介绍和接入
  3. 解决Idea中yml文件不显示小绿叶图标
  4. 阿拉丁2022 年度小程序白皮书发布,8 亿 DAU 再现小程序繁荣生态
  5. 【1235. 规划兼职工作】
  6. PG中XLOG日志结构
  7. 请听一个故事------三个70多岁老人的创业故事(励志)
  8. 服务器如何接收GPS定位器发送过来的数据
  9. 关于input在苹果和安卓手机上调用相机和相册的问题
  10. 升级!鹏业云计价i20(西藏)软件V11.0.27版本