⌨️动态format的printf

type
status
date
slug
summary
tags
category
icon
password

动态format的Printf

因为工作上的需要,要实现一个动态format的printf,即format在运行时才能知道,参数按4字节对齐放在一块buffer,运行时可以知道参数的个数,以及所有参数的指针。

想法一

参照printf的实现方式,手动解析format,自己实现一个printf的功能。不过leader说咱们不去做这个解析,容易出bug。

想法二

leader想要直接调用c printf,一把直接实现打印。但是这样有两个个问题:
  • printf是一个可变参数的函数,调用printf时,参数个数在编译期就确定了,但是我这里的format和参数个数都是需要在运行时才能获取到。
  • 和参数个数一样,调用printf时,参数类型也要在编译期就确定。
 

参数个数

于是想到规定printf参数的数量时有上限(比如32),通过一个switch表,根据参数的数量执行对应的printf。

参数类型

调用函数传参时,无非就是将对应参数压到栈上(或寄存器),然后被调用的函数通过一些手段从栈上获取参数的值,因此参数的类型或许没有那么大影响,并且printf支持的类型就只有那几种基本类型,我是不是能把所有参数都通过int64_t类型传进去?于是做了下实验。
输出:
这个思路看起似乎是ok的,不过调整下%f的位置,就出现了问题:
输出:
可以发现输出出现了错误,可是为什么呢,这和可变参数函数的实现方式以及我是跑在64位机器上有关系。

想法三,va_list

上面的思路走不通,于是去仔细了解了下可变参数函数的实现,准备从这里入手。

传参方式

首先咱们都知道,在32位机器上面,传参是通过栈传递的。
而到了64位机器上,查询资料后知道,前六个参数通过寄存器传递,剩下再通过栈传递。不过如果是这样的话,不能解释上面出现的问题。
先放着这个疑问不管,继续了解可变参数函数的实现,其中最关键的就是va_list这个结构。

va_list原理

参照在我的工作环境上,va_list的结构如下:
如何确定这个结构,和其中字段的意义可以参照这篇文章。
这篇文章较为详细地解释了各个字段的意义,和va_list的工作原理。
因为fp_offset字段的存在,我猜测是不是在传参时,64位平台下,针对浮点类型的变量,会优先放到浮点寄存器里面,超过上限的浮点参数再放到栈上。这样就可以解释之前为什么%f换个位置结果就出错了。

构造va_list

知道va_list的工作原理后,就可以自己构造一个va_list了。
stackoverflow上已经这个问题了,也有人给出了一个可以运行的代码。
高赞回答的代码里,思路也很简单。
  1. 申请一块buffer作为overflow_area
  1. 把所有的参数都按4字节对齐放在overflow_area
  1. 设置gp_offset为48, fp_offset为304,这样就会直接从overflow_area开始取数据。

deafult argument promotions

够造好va_list后,直接调用vprintf,就能一把进行打印了。
但经过测试后,发现打印float类型时还是有问题,debug后发现数据也没问题。于是准备自己写个可变参数函数,手动解析下自己构造的va_list看看问题。然后在取float类型的数据的时候,编译器发出了警告。
💡
Warning. Second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behaviour because arguments will be promoted to 'double'
警告说float会被promote成double,经过一番查询资料,了解到原来在可变参数函数传参时,float会先被promote为double。
其实不止float和被promote,bool, char, short都会被promote为int,只不过因为在构造va_list时,参数是按4字节对齐放置的,相当于进行了一次promote。而float和double的格式不一样,因此解析的时候也就不对了。
在构造va_list时,针对float类型的参数,手动promote后再放到overflow_area,问题就解决了。
 
MLIR Pass机制Reading Notes
Loading...
Catalog