【问题标题】:I'm implementing printf function in C/Linux我在 C/Linux 中实现 printf 函数
【发布时间】:2017-09-27 23:13:32
【问题描述】:

程序:

 #ifndef PRINTF_H
 #define PRINTF_H
 #include "my_put_char.h"

 int my_printf(char *str, ...);

 #endif

这是我的函数的头文件。

#include <stdio.h>
#include "my_put_char.h"

void my_put_char(char c) 
{
     fwrite(&c, sizeof(char), 1, stdout);
}

这是我的 putchar 实现(my_put_char.c)。

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "printf.h"

int my_printf(char *str, ...)
{   
    if(str == NULL)
        return 0;

    int i;
    char a;

    va_list print;
    va_start(print,str);

    for(i = 0; str[i] ; i++)
    {
        if(str[i] == '%')
        {
            i++;
            switch(str[i])
            {
                case 'c':
                a = va_arg(print, char);
                my_put_char(a);
                break;
            }
        }
     }
     va_end(print);
     return 0;
}

最后,这是我的 printf 实现的一部分。 我正在使用%c 进行测试以显示一个字符。

当我从main.cmy_print("%c", 'd'); 时 它编译并显示d

但是当我执行 my_print("%c", "hi"); 时,它仍然会编译并显示一个数字。

问题:

a = va_arg(print, char);之后(或之前)有没有办法检查我的输入是否是不同的数据类型? 如果我的输入是不同的数据类型,我会尝试显示错误。

我在这个主题上研究了 2 天,但找不到任何答案。 非常感谢您的宝贵时间!

【问题讨论】:

  • 无法确定类型。看看this。 (它是 cppreference,但在这里无关紧要。)具体来说,它说“如果 ap 中的下一个参数的类型(在提升之后)与 T 不兼容,则行为未定义”。
  • @Yunnosch,感谢您编辑这篇文章!
  • 为什么你的printf.h 标头包含my_put_char.h 标头? printf.h 的用户会使用其中声明的内容吗?注意格式字符串应该是const char *fmt(加上const); C99、C11 和 POSIX 也可以使用 restrict (int printf(const char * restrict format, ...);) 对其进行限定。您的实现文件应包含您的 my_put_char.h 标头;它调用函数。您的其他标题应该(可能)不包括它。
  • 请注意,当使用 va_arg 时,您必须指定提升类型 — intunsigned intdouble,而不是更短的类型(charshortfloat , ETC)。您的行 a = va_arg(print, char); 正在立即调用未定义的行为。 (标准§7.16.1.1 中的措辞是:…或者如果类型与实际下一个参数的类型不兼容(根据默认参数提升),则行为未定义,除了… i> 并且例外情况不适用于此处(它们与有符号整数和无符号整数以及 void *char * 有关)。
  • GCC 编译器支持 __attribute__((format(printf,1,2)) 符号来检查类似 printf() 的函数是有原因的——否则很难做到。

标签: c linux printf


【解决方案1】:

当我执行 my_print("%c", "hi"); 时,它仍然会编译并显示一个数字

你有一些undefined behavior,所以是scared。您的 my_printf 会使用错误类型的参数调用 va_arg(应将 char 提升为 int,得到 char*)。

要解释正在发生的事情,您应该深入了解实现细节(查看汇编代码,例如使用gcc -Wall -fverbose-asm -O -S;研究您的processorinstruction set architectureapplication binary interfacecalling conventions)。 您不想这样做,这可能需要数年时间并且无法重现。

立即阅读Lattner's blog on UB

然后下载C11规范n1570....

您还可以通过gcc 使用一些function attributes。不要忘记编译所有警告和调试信息 (gcc -Wall -Wextra -g)

写完a = va_arg(print, char);之后有没有办法检查我的输入是否是不同的数据类型?

不,不是真的,也不总是。但是format 函数属性可能会有所帮助。你也可以花几个月的时间用你自己的plugin 或一些GCC MELT 扩展来定制GCC(这不值得你花时间)。请注意Halting ProblemRice's Theorem(每个都使static 源代码program analysis 如此具有挑战性)。还可以查看Frama-C 等源分析工具。

我正在实现 printf 函数

顺便说一句,研究C standard library(例如GNU glibcmusl-libc)的现有free software 实现的源代码可能会很有启发性;它们基于syscalls(2)

【讨论】:

  • 非常感谢您的回答 Basile!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-10
  • 1970-01-01
  • 2016-02-21
  • 2014-03-02
  • 2018-12-21
相关资源
最近更新 更多