使用memccpy函数可以替代不安全的str[n]cpy、str[n]cat等
memccpy函数在POSIX标准库中,最近也加入了C23标准库中,而在MSVC的C运行库中是以下划线开头的形式_memccpy存在的,非下划线开头的函数memccpy当前是deprecated(以后可能会将这些函数去掉deprecated标记)
strcpy、strcat是缓冲区溢出的元凶之一,如果strcpy中的src,或strcat中src或dst,是用户输入,而目标缓冲区是定长或长度未被正确计算的话,这些函数是不安全的;strncpy虽然不会缓冲区溢出,但是设计上是用于0填充的定长字符串而不是空终止字符串,经常被误用导致字符串未正确终止或被填充的0截断;strncat虽然不会导致缓冲区溢出和字符串未正确终止,但是没有办法确定字符串是否被截断,也是不安全的
使用memccpy可以代替这些函数,并且只要检查返回值是否为NULL,就可以确定缓冲区是否不够,保证了安全性的同时编程又更加方便
复制或拼接长度已知不会导致溢出的字符串,仍然可以使用经典的strcpy、strcat等函数
这个函数没有宽字符串版本,因此无法在宽字符串上使用,宽字符上只能使用wmemchr+wmemcpy模拟这个函数的功能
--------------------------------
对于格式化,可以使用带缓冲区大小bufsz的格式化函数snprintf、swprintf,它们两个有细微的区别:
snprintf:格式化成功时,无论结果是否被截断,始终返回应写入的字符串长度(不包括终止0),格式化失败时,返回负值表示失败,应判断返回值ret满足条件ret >= 0 && ret <= bufsz - 1,以确保写入成功且未被截断,buffer和bufsz可使用NULL和0以提前确定缓冲区大小(ret + 1)
swprintf:格式化成功并且结果未被截断时,返回写入的字符串长度(不包括终止0),格式化失败或结果被截断时,返回负值表示失败,因此无法提前确定缓冲区大小
注意MSVC中带下划线的版本如_snprintf、_snwprintf与不带下划线的版本行为不同,带下划线的版本只有在buffer和count为NULL和0时返回应写入的字符串长度(不包括终止0),应写入的字符串长度小于count时返回写入的字符串长度(不包括终止0),写入的字符串0终止,正好是count时返回count,大于count则返回负值表示失败,写入的字符串被截断并且不是0终止,因此需要判断ret >= 0 && ret <= bufsz - 1以确定正确0终止并且未被截断
--------------------------------
定义于头文件 <string.h>
void *memccpy(void * restrict dest, const void * restrict src, int c, size_t count);
从 src
所指向的对象复制字符到 dest
所指向的对象,在满足接下来的任何两个条件之一后停止:
count
个字符转译 src
与 dest
对象为 unsigned char 的数组。
若符合任何以下条件则行为未定义:
dest
数组结尾的访问dest
或 src
为非法或空指针值dest | - | 指向要复制的对象的指针 |
src | - | 指向复制来源对象的指针 |
c | - | 终止字符,首先转换成 unsigned char |
count | - | 要复制的字符数 |
若找到字符 (unsigned char)c 则 memccpy
返回 dest
中 (unsigned char)c 后一字符的指针,否则返回空指针。
函数等同于 POSIX memccpy 。
memccpy(dest, src, 0, count) 表现类似 strncpy(dest, src, count) ,除了前者返回指向被写入缓冲区的末尾的指针,并且不会以零填充目标数组。从而 memccpy
对高效连接多个字符串有用。
char bigString[1000];
char* end = bigString + sizeof bigString;
char* p = memccpy(bigString, "John, ", 0, sizeof bigString);
if (p) p = memccpy(p - 1, "Paul, ", 0, end - p);
if (p) p = memccpy(p - 1, "George, ", 0, end - p);
if (p) p = memccpy(p - 1, "Joel ", 0, end - p);
puts(bigString); // John, Paul, George, Joel
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
const char src[] = "Stars: Altair, Sun, Vega.";
const char terminal[] = {':', ' ', ',', '.', '!'};
char dest[sizeof src];
const char alt = '@';
for (size_t i = 0; i != sizeof terminal; ++i) {
void *to = memccpy(dest, src, terminal[i], sizeof dest);
printf("Terminal '%c' (%s):\t\"", terminal[i], to ? "found" : "absent");
// 若找不到 `terminal` 字符 - 打印整个 `dest`
to = to ? to : dest + sizeof dest;
for (char *from = dest; from != to; ++from)
putchar(isprint(*from) ? *from : alt);
puts("\"");
}
puts("\n" "Separate star names from distances (ly):");
const char *star_distance[] = {
"Arcturus : 37", "Vega : 25", "Capella : 43", "Rigel : 860", "Procyon : 11"
};
char names_only[64];
char *first = names_only;
char *last = names_only + sizeof names_only;
for (size_t t = 0; t != (sizeof star_distance)/(sizeof star_distance[0]); ++t) {
if (first) {
first = memccpy(first, star_distance[t], ' ', last - first);
} else break;
}
if (first) {
*first = '\0';
puts(names_only);
} else {
puts("Buffer is too small.");
}
}
输出:
Terminal ':' (found): "Stars:"
Terminal ' ' (found): "Stars: "
Terminal ',' (found): "Stars: Altair,"
Terminal '.' (found): "Stars: Altair, Sun, Vega."
Terminal '!' (absent): "Stars: Altair, Sun, Vega.@"
Separate star names from distances (ly):
Arcturus Vega Capella Rigel Procyon
--------------------------------
参考资料:
wprintf, fwprintf, swprintf, wprintf_s, fwprintf_s, swprintf_s, snwprintf_s - cppreference.com
snprintf, _snprintf, _snprintf_l, _snwprintf, _snwprintf_l | Microsoft Docs