ZetCode

C vprintf 函数

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

可变参数处理是 C 编程中的一项强大功能,能够实现灵活的格式化输出函数。vprintf 函数是 printf 的可变参数版本,它接受一个 va_list 参数。本教程将深入讲解 vprintf,演示其用法,并提供实际示例。理解 vprintf 有助于您创建自定义的格式化输出函数并安全地处理可变参数。

什么是 vprintf?

C 中的 vprintf 函数执行像 printf 一样的格式化输出,但它接受一个 va_list 而不是可变参数。它在 stdarg.h 中声明,并遵循与 printf 相同的格式说明符规则。当围绕带可变参数的 printf 创建包装函数时,此函数特别有用。在使用 va_list 之前,请务必确保正确初始化,并使用 va_end 清理它。

基本 vprintf 示例

此示例演示了 vprintf 与可变参数的基本用法。

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

void print_message(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

int main() {
    print_message("Hello, %s! You have %d new messages.\n", 
                 "John", 3);
    return 0;
}

在此,print_message 是一个接受可变参数的包装函数。va_start 宏将 args 初始化为指向第一个可变参数。vprintfprintf 一样处理格式字符串和参数。最后,va_end 清理 va_list

创建自定义日志记录函数

此示例演示了如何使用 vprintf 创建带有时间戳的日志记录函数。

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

void log_message(const char *format, ...) {
    time_t now;
    time(&now);
    printf("[%.24s] ", ctime(&now));
    
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
    
    printf("\n");
}

int main() {
    log_message("System started with %d%% memory available", 75);
    log_message("User %s logged in from IP %s", "admin", "192.168.1.1");
    return 0;
}

log_message 函数使用 ctime 在每个日志条目前添加时间戳。然后它使用 vprintf 来处理可变参数和格式字符串。当您希望所有日志消息具有一致的格式时,这种模式在日志系统中很常见。

错误报告函数

此示例演示了如何创建错误报告函数,该函数使用 vprintfvfprintf 将输出写入 stderr 和日志文件。

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

void report_error(FILE *logfile, const char *format, ...) {
    va_list args;
    
    // Print to stderr
    va_start(args, format);
    fprintf(stderr, "ERROR: ");
    vfprintf(stderr, format, args);
    fprintf(stderr, "\n");
    va_end(args);
    
    // Print to log file if provided
    if (logfile != NULL) {
        va_start(args, format);
        fprintf(logfile, "ERROR: ");
        vfprintf(logfile, format, args);
        fprintf(logfile, "\n");
        va_end(args);
    }
}

int main() {
    FILE *log = fopen("error.log", "a");
    if (log == NULL) {
        report_error(NULL, "Failed to open log file");
        return 1;
    }
    
    report_error(log, "Invalid input value: %d", 42);
    report_error(log, "Connection timeout after %d seconds", 30);
    
    fclose(log);
    return 0;
}

report_error 函数演示了使用 vfprintfvprintf 的文件版本)将输出写入多个目标。请注意,我们必须为可变参数的每次使用调用 va_startva_end。该函数在尝试写入日志文件之前会检查日志文件指针是否有效。

安全与不安全的变参函数

此示例比较了使用 vprintf 的安全与不安全的变参函数用法。

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

// Unsafe: No way to verify argument types match format
void unsafe_print(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

// Safer: Use format string attribute to enable compiler checks
void safe_print(const char *format, ...) 
    __attribute__((format(printf, 1, 2)));

void safe_print(const char *format, ...) {
    va_list args;
    va_start(args, format);
    vprintf(format, args);
    va_end(args);
}

int main() {
    // Compiler will warn about this with safe_print:
    // safe_print("Number: %s\n", 42);  // Wrong format specifier
    
    unsafe_print("Number: %s\n", 42);  // Undefined behavior
    safe_print("Number: %d\n", 42);    // Correct usage
    return 0;
}

该示例显示了一个打印函数的两个版本。不安全的版本没有针对格式说明符和参数不匹配的保护。更安全版本使用 format 属性(GCC/clang 扩展)来启用编译器检查。虽然 vprintf 本身在使用正确的情况下是安全的,但包装函数应包含验证以防止未定义行为。

格式化到字符串缓冲区

此示例演示了使用 vsnprintfvsprintf 的安全版本)格式化到字符串缓冲区。

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

int format_string(char *buffer, size_t size, const char *format, ...) {
    va_list args;
    va_start(args, format);
    int result = vsnprintf(buffer, size, format, args);
    va_end(args);
    
    if (result >= size) {
        // Truncation occurred
        buffer[size - 1] = '\0';
        return -1;
    }
    
    return result;
}

int main() {
    char buffer[64];
    
    if (format_string(buffer, sizeof(buffer), "The answer is %d", 42) >= 0) {
        printf("Formatted string: '%s'\n", buffer);
    }
    
    // Test truncation
    if (format_string(buffer, 10, "This is too long for the buffer") < 0) {
        printf("String was truncated to: '%s'\n", buffer);
    }
    
    return 0;
}

format_string 函数使用 vsnprintf 将数据安全地格式化到指定大小的缓冲区中。它会检查截断并确保字符串始终以 null 结尾。这比不检查缓冲区边界的 vsprintf 安全得多。该函数返回写入的字符数(不包括 null 终止符),如果发生截断,则返回 -1。

使用 vprintf 的最佳实践

来源

C vprintf 文档

本教程探讨了 vprintf 函数及其变体,演示了如何在 C 中创建灵活的格式化输出函数。通过理解这些技术,您可以构建更健壮、更易于维护的代码,安全高效地处理可变参数。

作者

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

列表 C 标准库