特定的 C 函数如何工作?

发布于 2024-12-27 05:07:03 字数 534 浏览 2 评论 0原文

我正在尝试学习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 如何执行上述任务吗?

I am trying to learn C and am very confused already.

In the OOP languages i have used there exists the ability to perform method overloading, where the same function could have different parameter types and call whichever was the most appropriate.

Now in C i know that this is not the case so i cant figure out the following problem, How printf() works.

For example:

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

Now as C does'nt support function overloading, How does printf manage to take any number of arguments, of any type, and then work correctly with them?

I have tried to find the printf() working by downloading the glibc source package but can quite seem to find it, though i'll keep looking.

Could anyone here explain how C performs the above task?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

坦然微笑 2025-01-03 05:07:03

C 支持一种称为“varargs”的函数签名类型,意思是“可变(数量)参数”。这样的函数必须至少有一个必需的参数。对于printf,格式字符串是必需的参数。

通常,在基于堆栈的计算机上,当您调用任何 C 函数时,参数会从右到左压入堆栈。这样,函数的第一个参数就位于堆栈的“顶部”,就在返回地址之后。

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

关键点是:

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

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

存储 va_list 以便以后在 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 );
}

C supports a type of function signature called "varargs" meaning "variable (number of) arguments". Such a function must have at least one required argument. In the case of printf, the format string is a required argument.

Generally, on a stack-based machine, when you call any C function, the arguments are pushed onto the stack from right-to-left. In this way, the first argument to the function is that found on the "top" of the stack, just after the return address.

There are C macros defined which allow you to retrieve the variable arguments.

The key points are:

  • There is no type-safety for the variable arguments. In the case of printf(), if the format string is wrong, the code will read invalid results from memory, possibly crashing.
  • The variable arguments are read through a pointer which is incremented through the memory containing those arguments.
  • The argument pointer must be initialized with va_start, incremented with va_arg, and released with va_end.

I have posted a ton of code you may find interesting on the related question:

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

Here's a skeleton of a printf() which only formats integers ("%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 );
}
走野 2025-01-03 05:07:03

在内部,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

充实它确实涉及相当多的工作——处理字段宽度、精度、更多转换等。然而,这足以至少给出一个如何在函数内检索不同类型的不同参数的风格。

Internally, printf will (at least usually) use some macros from stdarg.h. The general idea is (a greatly expanded version of) something like this:

#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

Fleshing it out does involve quite a bit of work -- dealing with field width, precision, more conversions, etc. This is enough, however, to at least give a flavor of how you retrieve varying arguments of varying types inside your function.

暖心男生 2025-01-03 05:07:03

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

这里有人能解释一下 C 是如何执行上述任务的吗?

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

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

所以基本上,如果类型不完全匹配,您将得到严重乱码的数据。如果你不幸的话,你将会有未定义的行为。

(Don't forget that, if you're using gcc (and g++?), you can pass -Wformat in the compiler options to get the compiler to check that the types of the arguments match the formatting. I hope other compilers have similar options.)

Could anyone here explain how C performs the above task?

Blind faith. It assumes that you have ensured that the types of the arguments match perfectly with the corresponding letters in your format string. When printf is called, all the arguments are represented in binary, unceremoniously concatenated together, and passed effectively as a single big argument to printf. If they don't match, you'll have problems. As printf iterates through the format string, every time it see %d it will take 4 bytes from the arguments (assuming 32-bit, it would be 8 bytes for 64-bit ints of course) and it will interpret them as an integer.

Now maybe you actually passed a double (typically taking up twice as much memory as an int), in which case printf will just take 32 of those bits and represented them as an integer. Then the next format field (maybe a %d) will take the rest of the double.

So basically, if the types don't match perfectly you'll get badly garbled data. And if you're unlucky you will have undefined behaviour.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文