C 中 printf() 函数的工作(仅可变数量的参数)

Posted

技术标签:

【中文标题】C 中 printf() 函数的工作(仅可变数量的参数)【英文标题】:Working of printf() function in C (Only Variable number of Arguments) 【发布时间】:2014-07-22 06:06:16 【问题描述】:

我问这个问题是为了理解 printf 作为一个接受可变长度参数的函数的工作原理。

我正在从here 学习可变数量的参数概念,让我感到困惑的是传入 va_arg(va_list,datatype) 的数据类型。我的意思是他们在这里提到了一种数据类型。我们需要传递具有不同数据类型的参数的情况呢? printf 函数也是如此。

printf 如何准确地计算出不同类型的参数类型。根据我的想法,他们必须检查第一个 const char* 参数中的每个 % 符号,然后检查特定数据类型的标记。

【问题讨论】:

快速链接帮助***.com/questions/2457656/… @Jayesh 感谢您的链接,但我不是在寻找硬件级别。只有 va_arg() 中的数据类型标识。我的意思是他们如何在多个不同的数据类型参数中实际识别特定的数据类型 是的,它会检查 %X 以查看要使用的数据类型或标志。没有这个,它就无法知道。唯一的方法是使用 C++ 的可变参数模板。 printf 仅从格式说明符中知道类型。相关:Why do I have to specify data type each time in C?. 顺便说一句,如果您使用 C++ 编写,请使用流而不是 C 中的旧函数 【参考方案1】:

以下变量参数列表函数解释了printf 的工作原理。

#include <stdio.h>
#include <stdarg.h>
main()

// call the foo function here
// like foo("%d%s%c",3,"hai",'a');


   void foo(char *fmt, ...) // This Function works like same as printf
   
       va_list ap;
       int d;
       char c, *s;

       va_start(ap, fmt);
       while (*fmt)
           switch (*fmt++) 
           case 's':              /* string */
               s = va_arg(ap, char *);
               printf("string %s\n", s);
               break;
           case 'd':              /* int */
               d = va_arg(ap, int);
               printf("int %d\n", d);
               break;
           case 'c':              /* char */
               /* need a cast here since va_arg only
                  takes fully promoted types */
               c = (char) va_arg(ap, int);
               printf("char %c\n", c);
               break;
           
       va_end(ap);
   

更多参考请参见va_arg的手册页

它不支持charfloat 值,所以我们需要对它进行类型转换。 对于float,您需要键入转换double 值。

【讨论】:

最好能把va_argman page做成一个链接。 那将是 foo("dsc" 而不是 foo("%d%s%c" 另外,要么函数声明的顺序必须颠倒,要么你需要上面的函数原型。【参考方案2】:

printf 是一个函数,而不是宏。它被定义为

int printf(const char *, ...)

并且有可变数量的参数。

printf 使用字符串来定义传递的参数数量。每个 % 用于向堆栈移动并检索参数。

所以,如果你通过了

"%d %d %d", 1, 2

然后将显示 1、2 和任意值。这很糟糕:你可以使用这个函数遍历堆栈。

通过时

"%d %d", 1, 2, 3

然后会显示 1 和 2。并且行为未定义:通常使用 __cdecl 调用约定,因此堆栈不会因为被调用者清理而损坏。

【讨论】:

如果想检查什么,请参考cplusplus.com。他们通常对所有库都有解释。 @anujpradhan “我认为 printf 是从 _printf 派生的”——不知道你的意思是什么,但是……不。 printf 从 C 的早期就已经存在,并且不是从其他函数派生的。【参考方案3】:

是的,它会检查 %X 以查看要使用的数据类型或标志。没有这个,它就无法知道。唯一的方法是使用 C++ 的可变参数模板。除此之外,C 如下所示..

此示例使用fwrite 将数据写入stdout

#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cstdarg>

void C_Printf(const char *fmt, ...)

    int fmtLength = strlen(fmt);
    va_list VariableArgs;
    va_start(VariableArgs, fmt);

    for (int I = 0; I < fmtLength; I++)
    
        if (fmt[I] == '%')
        
            switch(tolower(fmt[++I]))
            
                case 'f':
                
                    double d = va_arg(VariableArgs, double);
                    fwrite(&d, sizeof(double), 1, stdout);
                
                break;

                case 'i':
                case 'd':
                
                    int i = va_arg(VariableArgs, int);
                    fwrite(&i, sizeof(int), 1, stdout);
                
                break;

                case 's':
                
                    const char *str = va_arg(VariableArgs, const char *);
                    fwrite(&str[0], sizeof(char), strlen(str), stdout);
                
                break;

                default:
                    break;
            
        
        else
            fwrite(&fmt[I], sizeof(char), 1, stdout);
    
    va_end(VariableArgs);


int main()

    C_Printf("%s", "hello there");

【讨论】:

【参考方案4】:

在某些free software 标准C 库实现中查看printf 的源代码,例如GNU libc 或 musl-libc。我发现musl-libc 非常易读,请查看src/stdio/printf.c 内部,然后查看src/stdio/vfprintf.c(实际工作完成的地方)。当然,它根据格式控制字符串使用va_arg(请参阅stdarg(3)。请注意,va_arg 是在编译器内部通过__builtin_va_arg 在GCC 中实现的)。 GCC 还内置了对printf 的支持

【讨论】:

以上是关于C 中 printf() 函数的工作(仅可变数量的参数)的主要内容,如果未能解决你的问题,请参考以下文章

C语言-函数的可变形参(不定形参)

C:将可变数量的参数从一个函数传递到另一个函数

c语言可变参数是干啥的

C、处理可变参数函数

C++不定参数

C语言可变参数