简体   繁体   English

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

[英]Working of printf() function in C (Only Variable number of Arguments)

I am asking this question to understand the working of printf as a function which is accepting variable length arguments.我问这个问题是为了理解printf作为一个接受可变长度参数的函数的工作原理。

I am learning the variable number of argument concept from here and what confused me is the datatype passing in va_arg(va_list,datatype) .我正在从这里学习可变数量的参数概念,让我感到困惑的是传入va_arg(va_list,datatype)的数据类型。 I mean they are mentioning one data type here.我的意思是他们在这里提到了一种数据类型。 What about the case where we need to pass arguments with different datatypes.我们需要传递具有不同数据类型的参数的情况呢? The same thing is done in printf function.printf函数中也是如此。

How exactly printf figures out the different type of argument types. printf 如何准确计算出不同类型的参数类型。 As per my thinking they must be checking every % symbol in the first const char* argument and after that the token checking for particular datatypes.根据我的想法,他们必须检查第一个 const char* 参数中的每个 % 符号,然后检查特定数据类型的标记。

The Following Variable argument list function explains you how the printf will work.以下变量参数列表函数向您解释了printf将如何工作。

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

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);
}

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

For more reference see the man page of va_arg有关更多参考,请参阅va_arg的手册页

It won't support char and float values, so we need to typecast it.它不支持charfloat值,因此我们需要对其进行类型转换。 For float you need to typecast the double values.对于float ,您需要对double精度值进行类型转换。

printf is a function, not a macro. printf是一个函数,而不是宏。 It is defined as它被定义为

int printf(const char *, ...)

and has variable number of arguments.并且有可变数量的参数。

printf use string to define number of arguments passed. printf使用字符串来定义传递的参数数量。 Each % is used to move towards the stack and retrieve arguments.每个 % 用于向堆栈移动并检索参数。

So, if you passed所以,如果你通过

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

then 1, 2, and arbitrary value would be displayed.然后将显示 1、2 和任意值。 That is bad: you can walk down the stack using this function.这很糟糕:你可以使用这个函数沿着堆栈向下走。

When passed通过时

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

then 1 and 2 would be displayed.然后将显示 1 和 2。 And behaviour is undefined: usually __cdecl calling convention is used, so stack wouldn't be corrupted because cleaned by caller.并且行为未定义:通常使用 __cdecl 调用约定,因此堆栈不会因为被调用者清理而损坏。

Yes it checks the %X to see what data type or flags to use.是的,它会检查 %X 以查看要使用的数据类型或标志。 Without this, it cannot know.没有这个,它就无法知道。 The only way is if you use C++'s variadic templates.唯一的方法是使用 C++ 的可变参数模板。 Other than that, C does it like below..除此之外,C如下所示..

This example uses fwrite to write the data to stdout .此示例使用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");
}

Look into the source code of printf in some free software standard C library implementation, eg GNU libc or musl-libc .在一些自由软件标准 C 库实现中查看printf的源代码,例如GNU libcmusl-libc I find musl-libc very readable, look inside src/stdio/printf.c then src/stdio/vfprintf.c (where the real work is done).我发现musl-libc非常易读,查看src/stdio/printf.c然后查看src/stdio/vfprintf.c (真正的工作完成的地方)。 Of course it uses va_arg according to the format control string (see stdarg(3) . Notice that va_arg is implemented inside the compiler, thru __builtin_va_arg in GCC ) .当然它根据格式控制字符串使用va_arg (参见stdarg(3) 。注意va_arg是在编译器内部实现的,通过GCC中的__builtin_va_arg )。 GCC also has builtin support for printf GCC 还内置了对printf的支持

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM