如何在不担心缓冲区溢出的情况下使用strncat?

13

我有一个缓冲区,我正在进行很多 strncat 操作。我想确保不会溢出缓冲区大小。

char buff[64];

strcpy(buff, "String 1");

strncat(buff, "String 2", sizeof(buff));

strncat(buff, "String 3", sizeof(buff));

我想用buff - xxx代替sizeof(buff),以确保我不会覆盖缓冲区。

6个回答

22

需要考虑现有字符串的大小以及空终止符

#define BUFFER_SIZE 64
char buff[BUFFER_SIZE];

//Use strncpy
strncpy(buff, "String 1", BUFFER_SIZE - 1);
buff[BUFFER_SIZE - 1] = '\0';

strncat(buff, "String 2", BUFFER_SIZE - strlen(buff) - 1);

strncat(buff, "String 3", BUFFER_SIZE - strlen(buff) - 1);

4
由于strncat函数总是在末尾添加空字符,因此不需要将buff[BUFFER_SIZE - 1]赋值为'\0'。 - fliX
5
如果使用strncpy函数时,在复制完字符串src之前就达到了指定的复制字符数count,那么生成的字符数组将不会以null结尾。但对于这个特定的例子,当BUFFER_SIZE=64且"String 1"较短时,您是正确的,因为strncpy函数也会复制null字符:http://en.cppreference.com/w/cpp/string/byte/strncpy - Soonts

15
为什么不使用snprintf?与strncat不同,它需要缓冲区的大小,但更重要的是,没有隐藏的O(n)。Strcat需要在每个连接的字符串上查找空终止符,并且每次运行时通过整个缓冲区查找末尾。随着字符串变得越来越长,strcat会变慢。另一方面,sprintf可以跟踪末尾,你会发现...
snprintf(buf, sizeof buf, "%s%s%s", "String1", "String2", "String3");

经常是更快、更易读的解决方案。


听起来不错。但是我有几个缓冲区/字符串需要写入一个全局缓冲区。这也意味着要等到所有的字符串/缓冲区都可用才能使用snprintf,否则缓冲区会被覆盖。 - jscode
3
如果您不想等待,snprintf函数会返回写入的字符数,因此您可以存储缓冲区偏移量并使用 offset+=snprintf(buf+offset, (sizeof buf)-offset, "%s", "String2") 进行操作。 - Dave
这个答案很有力量。strcat函数隐式搜索NULL终止符。 - Xofo

7
在您的原始代码中,使用strncat函数的方式实际上适用于另一个函数:strlcat(注意是l而不是n)。strlcat函数不是标准函数,但它是一个流行的实现提供的strncat替代品。 strlcat期望作为其最后一个参数提供整个目标缓冲区的总大小。
同时,strncat期望作为其第三个参数提供目标缓冲区剩余未使用部分的大小。因此,您的原始代码是错误的。
我建议您不要像Joe的回答中那样对strncpy进行恶劣的滥用,并使用那些strlen调用进行显式重新扫描,而是使用实现提供的strlcat或自己实现一个(如果您的实现没有提供strlcat)。 http://en.wikipedia.org/wiki/Strlcpy

6

这是最好的方法。如果您没有在本地分配(在这种情况下,您已经在本地分配),sizeof()只会给出指向数据的指针大小,但最好用这种方式处理,如果代码重构,它仍然可以工作。

#define MAXBUFFSIZE 64

char buff[MAXBUFFSIZE];

buff[0] = 0;  // or some string

strncat(buff, "String x",MAXBUFFSIZE - strlen(buff) - 1);

在这种情况下,sizeof 将给出整个缓冲区的大小,因为它是一个数组,而不是动态分配的内存块。 - yan
@Hogan:不是这样的。当sizeof应用于数组对象时,它会计算出整个数组对象的总大小。在OP的代码中没有任何形式的“指向数据”的指针。 - AnT stands with Russia
我喜欢被踩票,因为被盗用的最佳答案是在我发布后至少一分钟才发布的。 - Hogan
@yan --对于本地缓冲区的正确指向--我修改了以指出我的真正观点。如果重新设计为进行分配,那么将出现丑陋的错误。 - Hogan
@andreyt - 请看上面的评论--似乎我无法通知两个人。 - Hogan

1
Hogan已经充分回答了这个问题;然而,如果你担心strcat(...)中的缓冲区溢出,那么你同样应该担心所有其他字符串函数中的缓冲区溢出。
使用strnlen(...)strncpy(...)确保您真正留在缓冲区范围内。如果没有strnlen(...)函数,请编写它。

1
strnlenstrncpy都是用于处理定长字符串的函数,与空字符结尾的字符串无关。与此同时,根据问题描述,OP特别关注空字符结尾的字符串。确实,我们经常看到strncpy被错误地用于空字符结尾的字符串。但是,strnlen在这里的作用完全不清楚。 - AnT stands with Russia
一般来说,将以空字符结尾的字符串视为可能受到固定长度字符串的限制是防止缓冲区溢出的方法。当依赖于空终止符(通过使用非-n函数)时,您依赖于空终止符距离字符串开头的距离仅有那么远,这可能导致您要复制到的缓冲区溢出(如果假设关于空终止字符不成立)。 - Edwin Buck

0
在这种情况下,我会使用memccpy而不是strncat——它更安全,速度也更快。 (它还比Dave提到的snprintf方法更快mentioned):
/**
 * Returns the number of bytes copied (not including terminating '\0').
 * Always terminates @buf with '\0'.
 */ 
int add_strings(char *buf, int len)
{
    char *p = buf;

    if (len <= 0)
        return 0;

    p[len - 1] = '\0'; /* always terminate */

    p = memccpy(buf, "String 1", '\0', len - 1);
    if (p == NULL)
        return len - 1;

    p = memccpy(p - 1, "String 2", '\0', len - 1 - (p - buf));
    if (p == NULL)
        return len - 1;

    p = memccpy(p - 1, "String 3", '\0', len - 1 - (p - buf));

    return (p == NULL ? len : p - buf) - 1;
}

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