特定的C功能如何工作?

时间:2012-01-16 17:32:27

标签: c printf overloading

我正在努力学习C并且我已经非常困惑。

在我使用的OOP语言中,存在执行方法重载的能力,其中相同的函数可以具有不同的参数类型并且以最合适的方式调用。

现在在C中我知道情况并非如此,所以我无法弄清楚以下问题,printf()如何工作。

例如:

char chVar = 'A';
int intVar = 123;
float flVar = 99.999;

printf("%c - %i - %f \n",chVar, intVar, flVar);
printf("%i - %f - %c \n",intVar, flVar, chVar);
printf("%f - %c - %i \n",flVar, chVar, intVar);

现在因为C不支持函数重载,printf如何设法获取任何类型的任意数量的参数,然后与它们一起正常工作?

我试图通过下载glibc源程序包找到printf()工作,但似乎很容易找到它,但我会继续寻找。

这里有人可以解释C如何执行上述任务吗?

3 个答案:

答案 0 :(得分:14)

C支持一种名为" varargs"的函数签名。含义"变量(数量)参数"。这样的函数必须至少有一个必需的参数。在printf的情况下,格式字符串是必需参数。

通常,在基于堆栈的计算机上,当您调用任何C函数时,参数将从右向左推入堆栈。通过这种方式,函数的第一个参数是在" top"堆栈,就在返回地址之后。

定义了C宏,允许您检索变量参数。

关键点是:

  • 变量参数没有类型安全性。在printf()的情况下,如果格式字符串错误,代码将从内存中读取无效结果,可能会崩溃。
  • 通过指针读取变量参数,该指针通过包含这些参数的内存递增。
  • 必须使用va_start初始化参数指针,使用va_arg递增,并使用va_end发布。

我发布了大量您可能会对相关问题感兴趣的代码:

Best Way to Store a va_list for Later Use in C/C++

这里是printf()的骨架,它只格式化整数("%d"):

int printf( const char * fmt, ... )
{
    int d;  /* Used to store any int arguments. */
    va_list args;  /* Used as a pointer to the next variable argument. */

    va_start( args, fmt );  /* Initialize the pointer to arguments. */

    while (*fmt)
    {
        if ('%' == *fmt)
        {
            fmt ++;

            switch (*fmt)
            {
                 case 'd':  /* Format string says 'd'. */
                            /* ASSUME there is an integer at the args pointer. */

                     d = va_arg( args, int);
                     /* Print the integer stored in d... */
                     break;
             }
        }
        else 
           /* Not a format character, copy it to output. */
        fmt++;
    }

    va_end( args );
}

答案 1 :(得分:5)

在内部,printf将(至少通常)使用stdarg.h中的一些宏。一般的想法是(一个大大扩展的版本)这样的东西:

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

int my_vfprintf(FILE *file, char const *fmt, va_list arg) {

    int int_temp;
    char char_temp;
    char *string_temp;
    char ch;
    int length = 0;

    char buffer[512];

    while ( ch = *fmt++) {
        if ( '%' == ch ) {
            switch (ch = *fmt++) {
                /* %% - print out a single %    */
                case '%':
                    fputc('%', file);
                    length++;
                    break;

                /* %c: print out a character    */
                case 'c':
                    char_temp = va_arg(arg, int);
                    fputc(char_temp, file);
                    length++;
                    break;

                /* %s: print out a string       */
                case 's':
                    string_temp = va_arg(arg, char *);
                    fputs(string_temp, file);
                    length += strlen(string_temp);
                    break;

                /* %d: print out an int         */
                case 'd':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 10);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;

                /* %x: print out an int in hex  */
                case 'x':
                    int_temp = va_arg(arg, int);
                    itoa(int_temp, buffer, 16);
                    fputs(buffer, file);
                    length += strlen(buffer);
                    break;
            }
        }
        else {
            putc(ch, file);
            length++;
        }
    }
    return length;
}

int my_printf(char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(stdout, fmt, arg);
    va_end(arg);
    return length;
}

int my_fprintf(FILE *file, char const *fmt, ...) {
    va_list arg;
    int length;

    va_start(arg, fmt);
    length = my_vfprintf(file, fmt, arg);
    va_end(arg);
    return length;
}


#ifdef TEST 

int main() {
    my_printf("%s", "Some string");
    return 0;
}

#endif

充实它确实涉及相当多的工作 - 处理字段宽度,精度,更多转换等等。然而,这足以让你了解如何在你的内部检索不同类型的不同参数。功能

答案 2 :(得分:2)

(不要忘记,如果你正在使用gcc(和g ++?),你可以在编译器选项中传递-Wformat,让编译器检查参数的类型是否与格式匹配。我希望其他编译器有类似的选择。)

  

这里有人可以解释C如何执行上述任务吗?

盲目的信仰。它假定您已确保参数类型与格式字符串中的相应字母完全匹配。调用printf时,所有参数都以二进制表示,毫不客气地连接在一起,并作为printf的单个大参数有效传递。如果它们不匹配,你就会遇到问题。当printf遍历格式字符串时,每次看到%d时,它将从参数中获取4个字节(假设32位,当然,对于64位整数,它将是8个字节)并且它将它们解释为整数。

现在也许你实际上传递了double(通常占用的内存是int的两倍),在这种情况下,printf将只占用其中的32位并将其表示为整数。然后下一个格式字段(可能是%d)将占用双倍的其余部分。

所以基本上,如果类型不完全匹配,你会得到严重乱码的数据。如果你运气不好,你会有不确定的行为。