ZetCode

C vsprintf 函数

最后修改日期:2025 年 4 月 6 日

格式化字符串输出是 C 编程的基石,能够实现灵活的文本生成。vsprintf 函数提供了强大的格式化功能,支持可变参数。本教程将深入探讨 vsprintf,解释它与 va_list 的关系,并展示实际用例。我们还将介绍更安全的替代方案,如 vsnprintf,以防止生产代码中的缓冲区溢出。

什么是 vsprintf?

vsprintf 函数使用 va_list 参数列表来格式化并在一块缓冲区中存储一系列字符。它是 sprintf 的可变参数版本。该函数接收一个格式字符串、一个缓冲区和一个 va_list 参数列表。与 sprintf 不同,它设计用于接受可变参数的函数。但是,它不检查缓冲区大小,这使得 vsnprintf 在大多数情况下更安全。

基本的 vsprintf 示例

此示例演示了 vsprintf 使用可变参数格式化字符串的基本用法。

basic_vsprintf.c
#include <stdio.h>
#include <stdarg.h>

void formatMessage(char *buffer, const char *format, ...) {
    va_list args;
    va_start(args, format);
    vsprintf(buffer, format, args);
    va_end(args);
}

int main() {
    char buffer[100];
    formatMessage(buffer, "Hello, %s! Today is %d-%02d-%02d.", 
                 "John", 2025, 4, 6);
    
    printf("%s\n", buffer);
    return 0;
}

formatMessage 函数使用 vsprintf 来格式化带可变参数的字符串。va_start 初始化参数列表,va_end 清理它。格式化后的字符串存储在提供的缓冲区中。请注意固定大小的缓冲区(100 个字符),如果格式化后的字符串超出此大小,可能会导致溢出。

带 vsnprintf 的安全替代方案

此示例展示了更安全的 vsnprintf 版本,它通过指定最大长度来防止缓冲区溢出。

safe_vsnprintf.c
#include <stdio.h>
#include <stdarg.h>

void safeFormat(char *buffer, size_t size, const char *format, ...) {
    va_list args;
    va_start(args, format);
    vsnprintf(buffer, size, format, args);
    va_end(args);
}

int main() {
    char buffer[20];
    safeFormat(buffer, sizeof(buffer), "The answer is %d", 42);
    
    printf("%s\n", buffer);
    return 0;
}

vsnprintf 是首选,因为它限制了写入缓冲区的字符数。第二个参数指定了缓冲区大小,从而防止溢出。此示例使用了一个小缓冲区(20 个字符)来演示安全机制。如果需要,该函数将截断输出,而不是导致缓冲区溢出。

创建自定义 printf 函数

了解如何围绕 vsprintf 创建一个包装函数来实现自定义日志记录功能。

custom_printf.c
#include <stdio.h>
#include <stdarg.h>
#include <time.h>

void logMessage(const char *format, ...) {
    char buffer[256];
    va_list args;
    
    // Get current time
    time_t now;
    time(&now);
    char *time_str = ctime(&now);
    time_str[strlen(time_str)-1] = '\0'; // Remove newline
    
    // Format the message
    va_start(args, format);
    vsprintf(buffer, format, args);
    va_end(args);
    
    printf("[%s] %s\n", time_str, buffer);
}

int main() {
    logMessage("Process started with PID: %d", 12345);
    logMessage("Warning: %s", "Low memory detected");
    return 0;
}

此示例创建了一个 logMessage 函数,该函数在日志消息前添加时间戳。它使用 vsprintf 来格式化可变参数,并将它们与时间戳信息结合起来。虽然这对于演示很有用,但生产代码应使用 vsnprintf 以保证安全。时间戳使用 time 获取,并使用 ctime 格式化。

使用 vsprintf 的错误处理函数

使用 vsprintf 实现一个可重用的错误报告函数,以一致地格式化错误消息。

error_handler.c
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>

void errorExit(const char *format, ...) {
    char buffer[256];
    va_list args;
    
    va_start(args, format);
    vsprintf(buffer, format, args);
    va_end(args);
    
    fprintf(stderr, "ERROR: %s\n", buffer);
    exit(EXIT_FAILURE);
}

int main() {
    int value = -1;
    
    if (value < 0) {
        errorExit("Invalid value %d (must be positive)", value);
    }
    
    return 0;
}

errorExit 函数使用 vsprintf 格式化错误消息并退出程序。它写入 stderr 以进行适当的错误流处理。这种模式有助于在整个应用程序中实现一致的错误报告。同样,生产代码应使用 vsnprintf 来防止错误消息中可能出现的缓冲区溢出。

使用多个 vsprintf 调用构建字符串

此示例演示了如何通过对同一缓冲区进行多次 vsprintf 调用来构建一个复杂的字符串。

multi_vsprintf.c
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

void appendFormatted(char *buffer, const char *format, ...) {
    va_list args;
    size_t len = strlen(buffer);
    
    va_start(args, format);
    vsprintf(buffer + len, format, args);
    va_end(args);
}

int main() {
    char buffer[256] = "Report: ";
    
    appendFormatted(buffer, "System status: %s. ", "OK");
    appendFormatted(buffer, "CPU usage: %d%%. ", 42);
    appendFormatted(buffer, "Memory: %dMB free.", 1024);
    
    printf("%s\n", buffer);
    return 0;
}

appendFormatted 函数通过计算当前长度并在正确的位置写入来将格式化文本附加到现有缓冲区。每次调用 vsprintf 都会向字符串添加内容。这项技术很强大,但需要仔细管理缓冲区大小。在此简单示例中,初始缓冲区足够大(256 个字符),可以容纳所有预期内容。

使用 vsprintf 的最佳实践

来源

C vsprintf 文档

本教程探讨了 vsprintf 函数及其更安全的替代方案 vsnprintf。这些函数是使用可变参数生成格式化字符串的强大工具。请记住,在生产代码中通过使用缓冲区大小检查来优先考虑安全性。

作者

我的名字是 Jan Bodnar,我是一名敬业的程序员,对编码充满热情。自 2007 年以来,我通过 1,400 多篇文章和 8 本电子书分享我的专业知识。凭借十多年的教学经验,我致力于让编程变得易于理解且引人入胜。

列表 C 标准库