问题
I want to delve into the implementation of function "printf" in C on macOS. "printf" uses the <stdarg.h> header file. I open the <stdarg.h> file and find that va_list is just a macro.
So, I am really curious about how the __builtin_va_list is implemented? I know it is compiler-specific. Where can I find the definition of the __builtin_va_list? Should I download the source code of clang compiler?
回答1:
So, I am really curious about how the __builtin_va_list is implemented?
__builtin_va_list is implemented inside the GCC compiler (or the Clang/LLVM one). So you should study the GCC compiler source code to understand details.
Look into gcc/builtins.def & gcc/builtins.c for more.
I am less familiar with Clang, which implements the same builtin.
But both GCC & Clang are open source or free software. They are complex beasts (several millions lines of code each), so you could need years of work to understand them.
Be aware that the ABI of your compiler matters. Look for example into X86 psABI for more details.
BTW, Grady Player commented:
Pops the correct number of bytes off of the stack for each of those tokens...
Unfortunately, today it is much more complex than that. On current processors and ABIs the calling conventions do use processor registers to pass some arguments (and the evil is in the details).
Should I download the source code of clang compiler?
Yes, and you also need to allocate several years of work to understand the details.
A few years ago, I did write some tutorial slides and links to external documentation regarding GCC implementation, see my GCC MELT documentation page (a bit rotten).
回答2:
if you look in the libc source this is the implementation for 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;
}
that doesn't tell you much... it is a variadic function taking at least one argument, the format... it makes a va_list with va_start and hands it to vfprintf which does the hard work.
it does this so it can share that hard work between all of the printf stream functions, but here is vfprintf in a source version of libc from git://sourceware.org/git/glibc.git, but the file is pretty hard to grok, because it is implemented with lots of macros.
but you can write you own variadic by scanning a format (here we wont scan a format but just trust an argument number passed in) function and acting on the 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;
}
It should be noted that this is a dangerous proposition... because va_arg, just pulls an arg off of the stack and doesn't have any state that can store information about bounds so: printNStrings(4, "how", "are", "you"); gets into stack overflow and UB territory quickly. The compiler's themselves have bounds checking for printf family functions... and you can invoke them on popular compilers with a declaration like: void mylogger(const char *format, ...) __attribute__((format(printf, 1, 2))); which will turn on warnings for the types passed into your logger.
I am still looking for the actual implimentation of va_list in glibc to use as a reference.
回答3:
This answer, for clang, just show how I find implementation of a builtin function.
I'm interested in implementation of std::atomic<T>. If T is not a trivial type, clang use a lock to guard its atomicity. Look this answer first, I find a builtin function named __c11_atomic_store. The question is, how this builtin function implemented in clang?
Searching Builtin in clang codebase, find in 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")
...
The keyword are AtomicExpr and CallExpr. Then I check every caller of AtomicExpr's constructor, but doesn't find any useful information. So I guess, maybe in parse phase, if parser match an builtin function calling, it will construct an CallExpr to AST with builtin flag. In code generate phase, it will emit the implementation.
Check CodeGen, I find the answer in lib/CodeGen/CGBuiltin.cpp and CodeGen/CGAtomic.cpp.
You can check CodeGenFunction::EmitVAArg, I holp that would be useful for you.
来源:https://stackoverflow.com/questions/49733154/how-is-builtin-va-list-implemented