Handmade Hero»Forums»Code
51 posts
Replacing cstdargs
Edited by vexe on
Greetings!

I'm trying to replace cstdargs and I got a working implementation after researching online and reading different book snippets. Here's what I have:

[Code]
typedef char *Va_list;

#define ALIGNMENT 8

#define BOUNDARY(X)\
((sizeof(X) + (ALIGNMENT - 1)) & ~(ALIGNMENT - 1))

#define Va_arg(ArgPointer, T)\
(* (T *)(((ArgPointer) += BOUNDARY(T)) - BOUNDARY(T)))

#define Va_end(ArgPointer)\
ArgPointer = 0

#define Va_start(ArgPointer, A)\
((ArgPointer) = (char *)&(A) + BOUNDARY(A))

int TryIt(char *Arg0, ...)
{
int ArgCount = 0;
Va_list ArgPointer;
Va_start (ArgPointer, Arg0);

for (; *Arg0; ++Arg0)
{
switch (*Arg0)
{
case 'd':
{
Assert(Va_arg(ArgPointer, int) == ++ArgCount);
} break;
case 'f':
{
Assert(Va_arg(ArgPointer, double) == ++ArgCount);
} break;
case 's' :
{
Assert(Va_arg(ArgPointer, char *)[0] == ++ArgCount);
} break;
case 'c' :
{
Assert(Va_arg(ArgPointer, char) == 48 + ++ArgCount);
} break;
}
}
Va_end (ArgPointer);

return(ArgCount);
}

void VargsTest()
{
Assert(TryIt("ddcfd", '\1', 2, '3', 4.0f, 5) == 5);
Assert(TryIt("") == 0);
Assert(TryIt("sfs", "\1", 2.0f, "\3") == 3);
}
[/Code]

I understand most of it, except for the BOUNDARY part. I know for sure Casey talked about this before but I can't recall which episode. I know what it's doing, but I don't understand 100% how it's doing it. i.e. why the "(size + alignment-1) & ~(alignment)" << that part I don't quite get. (I'm not even sure that BOUNDARY is the right name for what it's doing)

Another thing, I'm compiling in x64 mode so the alignment is 8 bytes, but I guess in 32-bit mode it's 4 bytes. Is there a macro in windows that tells us which mode we're compiling in? or do I just pass a BUILD_32 define or something to the compiler and check for #ifdef BUILD_32 in my code?

Any explanation is appreciated!

Thanks!
elxenoanizd/vexe
Mārtiņš Možeiko
2562 posts / 2 projects
Replacing cstdargs
Edited by Mārtiņš Možeiko on
This is one of the things you really should not be replacing. Unless you are writing your own compiler. Compiler knows how architecture works and can implement everything correctly. If you don't trust compiler, well then you have much bigger problem than stdargs.h.

Of course you can read manual for every architecture that you want to support, understand its C ABI, and implement this yourself. But compiler already does it for you - provides va_xxx compiler intrinsics in a same way how it provides SSE and other intrinsics. They are not part of C runtime library, they are part of compiler.

As for define - _M_IX86 and _M_AMD64. It's all in the documentation: https://msdn.microsoft.com/en-us/library/b0084kay.aspx
For gcc/clang it is __i386__ and __x86_64__ for Intel architecture. For ARM it is __aarch64__ for 64-bit otherwise it is 32-bit.
This is a good site for predefined macros: http://predef.sf.net
51 posts
Replacing cstdargs
Edited by vexe on
Thanks for the reply. I'm using va_xxx in my StringFormat replacement of sprintf which I mostly use this for debug/meta code so it's mostly code that runs on the dev machine.

So you're saying cstdargs is not part of the CRT library but part of the compiler, correct? I followed va_start and reached __crt_va_start then a "void __cdecl __va_start(va_list*, ...)" function, I guess that's the intrinsic part you're talking about?
Mārtiņš Možeiko
2562 posts / 2 projects
Replacing cstdargs
Yes, compiler replaces it with whatever machine instructions it needs to implement the behavior. In the same way how SSE intrinsic function _mm_sqrt_ps is replaced with just one SQRTPS instruction.

For clang it's pretty clear these are special builtin functions: https://llvm.org/svn/llvm-project/cfe/trunk/lib/Headers/stdarg.h
1
2
3
#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_end(ap)          __builtin_va_end(ap)
#define va_arg(ap, type)    __builtin_va_arg(ap, type)