如何使用sprintf连接字符串?

99

我遇到了一个关于 sprintf 的严重问题。

假设我的代码片段是:

sprintf(Buffer,"Hello World");
sprintf(Buffer,"Good Morning");
sprintf(Buffer,"Good Afternoon");
.
.
.

有一百个迭代……

如果我这样做,它会被覆盖。

如何使用sprintf避免覆盖?如果我在最后加上printf,我想要看到所有的行。


17
我将使用snprintf而不是sprintf, 我将使用printf("%s", str)而不是printf(str)。 - fa.
16个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
149

您需要:

sprintf(Buffer,"Hello World");
sprintf(Buffer + strlen(Buffer),"Good Morning");
sprintf(Buffer + strlen(Buffer),"Good Afternoon");

当然,你需要确保你的缓冲区足够大。


5
虽然我更喜欢Aeth的解决方案,但它似乎比每次重新计算字符串长度更有效率。 - extraneon
8
我见过一种类似的技巧,就是定义#define eos(s) ((s)+strlen(s)),或者声明一个函数也可以。然后你就可以使用sprintf(eos(Buffer), "Stuff") - clstrfsck
4
更简单的方法是,您可以直接使用sprintf(strchr(s, '\0'), "...") - Arto Bendiken
1
  • strlen(Buffer) 是用来追加到实际缓冲区的内容的吗?
- bretcj7
4
这是指针运算。就像将当前字符串长度加到“缓冲区”的起始地址上一样。这种操作对于多字节和Unicode字符串来说是不安全的。例如,获取Unicode字符串“Hello”的长度将返回5,但实际上在这种情况下需要*Buffer + 5 * sizeof(wchar_t)*。 - A.B.

84
int length = 0;
length += sprintf(Buffer+length, "Hello World");
length += sprintf(Buffer+length, "Good Morning");
length += sprintf(Buffer+length, "Good Afternoon");

以下是一个具有一定的错误防范能力的版本。如果您不关心错误何时发生,只要它们发生时您可以继续进行操作,该版本非常有用。

int bytes_added( int result_of_sprintf )
{
    return (result_of_sprintf > 0) ? result_of_sprintf : 0;
}

int length = 0;
length += bytes_added(sprintf(Buffer+length, "Hello World"));
length += bytes_added(sprintf(Buffer+length, "Good Morning"));
length += bytes_added(sprintf(Buffer+length, "Good Afternoon"));

1
但是如果sprintf遇到转换失败会发生什么? - anon
3
那么就会发生一些不好的事情。为了简洁起见,我省略了错误检查。 - Matthew T. Staebler
+1 - 额外的错误检查应该是读者的练习。毕竟,这是他们的代码 :) - Tim Post
如果sprintf的结果为负数,报告错误和errono可能是有意义的。 - Boris Ivanov

36

为了安全起见(避免缓冲区溢出),我建议使用snprintf()

const int MAX_BUF = 1000;
char* Buffer = malloc(MAX_BUF);

int length = 0;
length += snprintf(Buffer+length, MAX_BUF-length, "Hello World");
length += snprintf(Buffer+length, MAX_BUF-length, "Good Morning");
length += snprintf(Buffer+length, MAX_BUF-length, "Good Afternoon");

12
snprintf的第二个参数是无符号整型(size_t),这意味着如果长度大于MAX_BUF,那么MAX_BUF-length将会下溢,并且snprintf会在缓冲区之外写入数据,从而导致缓冲区溢出。请注意,snprintf返回的值等于如果有足够的空间可用时将要写入的字节数,而不是实际写入的字节数。 - leszek.hanusz

17

对于snprintf()snprintfcat()包装器:

size_t 
snprintfcat(
    char* buf,
    size_t bufSize,
    char const* fmt,
    ...)
{
    size_t result;
    va_list args;
    size_t len = strnlen( buf, bufSize);

    va_start( args, fmt);
    result = vsnprintf( buf + len, bufSize - len, fmt, args);
    va_end( args);

    return result + len;
}

12

使用 sprintf() 的返回值。

Buffer += sprintf(Buffer,"Hello World");
Buffer += sprintf(Buffer,"Good Morning");
Buffer += sprintf(Buffer,"Good Afternoon");

3
这是最佳解决方案,不需要使用strlen函数。 - tttony
5
请注意,您还需要保存缓冲区的起始地址以供进一步参考。 - bersanri

6

在需要进行字符串拼接时,为什么要使用sprintf而不是专为此目的设计的方法,例如strcatstrncat


17
可能这个例子只是一个简单的情况,仅涉及到字符串的拼接。问题可以扩展到包括其他类型的格式化数据拼接,这些数据不适用于strcat函数。 - Matthew T. Staebler

5

小型完整代码示例

仅使用平面的标准库stdio

#include <stdio.h>
int main()
    {
    char c[1024];
    int  i=0;

    i+=sprintf(c+i,"We "   );
    i+=sprintf(c+i,"Love " );
       sprintf(c+i,"Coding");

    printf("%s",c);
    }

输出:我们热爱编程


5
我发现下面的方法很好用。
sprintf(Buffer,"Hello World");
sprintf(&Buffer[strlen(Buffer)],"Good Morning");
sprintf(&Buffer[strlen(Buffer)],"Good Afternoon");

7
strlen 周围的 [...] 是什么意思?它应该改成 (...) 吗? - CodyChan
1
太多的Python :D - Niko

4
我认为您正在寻找fmemopen(3)函数:
#include <assert.h>
#include <stdio.h>

int main(void)
{
    char buf[128] = { 0 };
    FILE *fp = fmemopen(buf, sizeof(buf), "w");

    assert(fp);

    fprintf(fp, "Hello World!\n");
    fprintf(fp, "%s also work, of course.\n", "Format specifiers");
    fclose(fp);

    puts(buf);
    return 0;
}
如果动态存储更适合您的使用情况,您可以采用Liam的卓越建议,使用open_memstream(3)
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *buf;
    size_t size;
    FILE *fp = open_memstream(&buf, &size);

    assert(fp);

    fprintf(fp, "Hello World!\n");
    fprintf(fp, "%s also work, of course.\n", "Format specifiers");
    fclose(fp);

    puts(buf);
    free(buf);
    return 0;
}

1
这正是我一直在寻找的。在man fmemopen的建议下,我发现open_memstream更适合我的应用程序。请参阅GNU手册以获取示例。 - Liam
@Liam 太棒了,感谢你关于 open_memstream 的提示。我也添加了一个示例。 - wkz
很棒,除了在Windows上没有fmemopenopen_memstream - 7vujy0f0hy

3
你是简单地追加字符串文本吗?还是将追加各种数据类型(整数,浮点数等)?将这个操作抽象为自己的函数可能会更容易(以下假设为C99):
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

int appendToStr(char *target, size_t targetSize, const char * restrict format, ...)
{
  va_list args;
  char temp[targetSize];
  int result;

  va_start(args, format);
  result = vsnprintf(temp, targetSize, format, args);
  if (result != EOF)
  {
    if (strlen(temp) + strlen(target) > targetSize)
    {
      fprintf(stderr, "appendToStr: target buffer not large enough to hold additional string");
      return 0;
    }
    strcat(target, temp);
  }
  va_end(args);
  return result;
}

你可以这样使用它:

char target[100] = {0};
...
appendToStr(target, sizeof target, "%s %d %f\n", "This is a test", 42, 3.14159);
appendToStr(target, sizeof target, "blah blah blah");

该函数返回vsprintf中的值,大多数实现中该值是写入目标的字节数。这个实现有一些漏洞,但它应该能给你一些想法。


为什么不把目标的sizeof放在函数内部?为什么需要将其放在参数中? - valerij vasilcenko
@hellomyfriends:因为在这个函数中,target是一个指向char的指针,而不是一个char数组,所以sizeof只会返回指针的大小,而不是它所指向的数组的大小。 - John Bode

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