怎么样" __ builtin_va_list"实施?

时间:2018-04-09 12:37:03

标签: c gcc clang

我想深入研究函数的实现&#34; printf&#34;在COS上的macOS。 &#34; printf的&#34;使用<stdarg.h>头文件。我打开<stdarg.h>文件,发现va_list只是一个宏。

所以,我真的很好奇__builtin_va_list是如何实现的?我知道它是特定于编译器的。我在哪里可以找到__builtin_va_list的定义?我应该下载clang编译器的源代码吗?

4 个答案:

答案 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")
...

关键字为AtomicExprCallExpr。然后,我检查AtomicExpr的构造函数的每个调用者,但是没有找到任何有用的信息。因此,我想也许是在解析阶段,如果解析器匹配一个内置函数调用,它将使用内置标志构造一个CallExpr到AST。在代码生成阶段,它将发出实现。

选中CodeGen,我在lib/CodeGen/CGBuiltin.cppCodeGen/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_listva_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;