我想深入研究在macOS上用C语言实现函数"printf“。"printf“使用<stdarg.h>
头文件。我打开<stdarg.h>
文件,发现va_list
只是一个宏。
所以,我真的很好奇__builtin_va_list
是如何实现的?我知道它是特定于编译器的。在哪里可以找到__builtin_va_list
的定义?我应该下载clang编译器的源代码吗?
发布于 2018-04-09 22:19:25
如果您查看libc源代码,就会发现这是printf的实现:
整型
__printf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = vfprintf (stdout, format, arg);
va_end (arg);
return done;
}
这不能告诉你太多..。它是一个变量函数,至少有一个参数,格式...它使用va_start
生成一个va_list
,并将其交给vfprintf
来完成这项繁重的工作。
它这样做是为了可以在所有printf流函数之间分担繁重工作,但这里是来自git://sourceware.org/git/glibc.git
的libc源代码版本中的vfprintf
,但是该文件很难理解,因为它是使用许多宏实现的。
但是,您可以通过扫描一个格式(这里我们不扫描一个格式,但只信任传入的参数编号)函数并对args执行操作来编写您自己的可变变量
#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;
}
应该注意的是,这是一个危险的提议。因为va_arg只是从堆栈中拉出一个参数,并且没有任何状态可以存储有关边界的信息,所以:printNStrings(4, "how", "are", "you");
很快就会进入堆栈溢出和UB区域。编译器本身已经对printf家族函数进行了边界检查...您可以使用如下声明在流行的编译器上调用它们:void mylogger(const char *format, ...) __attribute__((format(printf, 1, 2)));
,它将打开对传递到记录器中的类型的警告。
我仍然在寻找va_list在glibc中的实际实现,以作为参考。
发布于 2018-07-25 17:28:45
这个答案,对于clang,只是展示了我如何找到一个内置函数的实现。
我对std::atomic<T>
的实现很感兴趣。如果T
不是一个简单类型,那么clang使用锁来保护它的原子性。首先,我找到了一个名为__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
,我希望这会对你有帮助。
发布于 2020-03-21 10:35:00
在Clang 9中,这是在
clang\lib\AST\ASTContext.cpp
调用图:
getVaListTagDecl
=>getBuiltinVaListDecl
=>CreateVaListDecl
=>Create***BuiltinVaListDecl
for example:
=>CreateCharPtrBuiltinVaListDecl
=>CreateCharPtrNamedVaListDecl
=>buildImplicitTypedef
当经过预处理的源代码中有__builtin_va_list时,编译器调用getVaListTagDecl来构建TypedefDecl AST节点并将其插入到AST中,TypedefDecl在任何源代码中都不存在,它是在构建过程中动态生成的,就好像源代码中有这样的内容:
typedef *** __builtin_va_list;
//for example
typedef char* __builtin_va_list;
https://stackoverflow.com/questions/49733154
复制相似问题