我想深入研究函数的实现&#34; printf&#34;在COS上的macOS。 &#34; printf的&#34;使用<stdarg.h>
头文件。我打开<stdarg.h>
文件,发现va_list
只是一个宏。
所以,我真的很好奇__builtin_va_list
是如何实现的?我知道它是特定于编译器的。我在哪里可以找到__builtin_va_list
的定义?我应该下载clang编译器的源代码吗?
答案 0 :(得分:5)
所以,我真的好奇__builtin_va_list是如何实现的?
__builtin_va_list
在<{3>}编译器(或GCC编译器)内部实现。因此,您应该研究GCC编译器源代码以了解详细信息。
了解Clang/LLVM&amp; gcc/builtins.def了解更多信息。
我不太熟悉gcc/builtins.c,它实现了相同的内置功能。
但GCC和&amp; Clang是开源或免费软件。它们是复杂的野兽(每行数百万行代码),因此您可能需要多年的工作才能理解它们。
请注意,编译器的Clang很重要。请查看ABI中的示例以获取更多详细信息。
BTW,X86 psABI评论道:
为每个令牌弹出正确的堆栈字节数...
不幸的是,今天它要复杂得多。在当前的处理器和ABI上,Grady Player确实使用calling conventions来传递一些参数(详情中有恶意)。
我应该下载clang编译器的源代码吗?
是的,你还需要分配几年的工作来了解细节。
几年前,我确实编写了一些教程幻灯片和指向GCC实施的外部文档的链接,请参阅我的processor registers页面(有点烂)。
答案 1 :(得分:2)
对于clang来说,这个答案只是说明我如何找到内置函数的实现。
我对std::atomic<T>
的实现感兴趣。如果T
不是普通类型,则clang使用锁来保护其原子性。首先看this answer,我发现一个名为__c11_atomic_store
的内置函数。问题是,如何在clang中实现此内置函数?
在clang代码库中搜索Builtin
,在clang/Basic/Builtins.def
中查找:
// Some of our atomics builtins are handled by AtomicExpr rather than
// as normal builtin CallExprs. This macro is used for such builtins.
#ifndef ATOMIC_BUILTIN
#define ATOMIC_BUILTIN(ID, TYPE, ATTRS) BUILTIN(ID, TYPE, ATTRS)
#endif
// C11 _Atomic operations for <stdatomic.h>.
ATOMIC_BUILTIN(__c11_atomic_init, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_load, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_store, "v.", "t")
ATOMIC_BUILTIN(__c11_atomic_exchange, "v.", "t")
...
关键字为AtomicExpr
和CallExpr
。然后,我检查AtomicExpr
的构造函数的每个调用者,但是没有找到任何有用的信息。因此,我想也许是在解析阶段,如果解析器匹配一个内置函数调用,它将使用内置标志构造一个CallExpr
到AST。在代码生成阶段,它将发出实现。
选中CodeGen
,我在lib/CodeGen/CGBuiltin.cpp
和CodeGen/CGAtomic.cpp
中找到答案。
您可以选中CodeGenFunction::EmitVAArg
,对您有用。
答案 2 :(得分:0)
如果查看libc源代码,这是printf的实现:
INT
__printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
这并没有告诉你多少......它是一个变量函数,至少采用一个参数,格式......它使va_list
与va_start
交叉并传递给{{1这是艰苦的工作。
它这样做是因为它可以在所有printf流函数之间共享那个艰苦的工作,但是来自vfprintf
的libc源代码中的vfprintf
,但该文件非常难以理解,因为它是用很多宏实现的。
但是你可以通过扫描一种格式(这里我们不会扫描一种格式但只是信任传入的参数号)函数并在args上作用来编写你自己的可变参数
git://sourceware.org/git/glibc.git
应该注意的是,这是一个危险的命题...因为va_arg,只是从堆栈中拉出arg并且没有任何可以存储有关边界的信息的状态,因此:#include <stdio.h>
#include <stdarg.h>
void printNStrings( int n, ...)
{
va_list l;
va_start(l,n);
for (int i=0; i< n; i++)
{
char * arg = va_arg(l, char *);
puts(arg);
}
va_end(l);
}
int main(void)
{
printNStrings(2,"hello", "world");
printNStrings(3, "how", "are", "you");
return 0;
}
进入堆栈溢出和UB领土很快。编译器本身已经检查了printf族函数...你可以在流行的编译器上调用它们,声明如下:printNStrings(4, "how", "are", "you");
这将打开传递给记录器的类型的警告。
我仍然在寻找glibc中va_list的实际用法作为参考。
答案 3 :(得分:0)
在Clang 9中,这是在
中实现的clang\lib\AST\ASTContext.cpp
调用图:
getVaListTagDecl
=>getBuiltinVaListDecl
=>CreateVaListDecl
=>Create***BuiltinVaListDecl
for example:
=>CreateCharPtrBuiltinVaListDecl
=>CreateCharPtrNamedVaListDecl
=>buildImplicitTypedef
当预处理的源代码中有__builtin_va_list时,编译器将调用getVaListTagDecl来构建TypedefDecl AST节点并将其插入AST中,该typedef在任何源代码中均不存在,它是在构建过程中动态生成的,就像在来源中是这样的:
typedef *** __builtin_va_list;
//for example
typedef char* __builtin_va_list;