|
Well, very useful I think.
But is there any other way to pass variable parameters to other?
According to your article, there are memory copy operations. Is there any other method without copying?
|
|
|
|
|
Here is a more straightforward implementation of your va_pass using only the standard compiler means, and it also does away with C++:
#include <stdarg.h>
#define VA_MAX_SIZE 256
struct _va_block_s {
char _dummy[VA_MAX_SIZE];
};
#define va_pass(args) va_arg(args, struct _va_block_s)
#include <stdio.h>
int my_printf(const char *format, ...) {
int ret_val;
va_list args;
va_start(args, format);
ret_val = printf(format, va_pass(args));
va_end(args);
return ret_val;
}
Like the original, va_pass just fetches a 256-byte block of memory from the parameter stack of the current variadic function (here, my_printf()); it is then pushed onto the stack for the variadic function we are calling (in this case, printf()). It's still not quite portable, since (1) it assumes the compiler is using the stack in a similar manner as in the x86 architecture, and (2) it assumes the calling convention of the parent call to be the same as that of the child. If the compiler uses some exotic calling convention that is not remotely similar to __cdecl---it may break.
With MSVC (and probably other x86 compilers), we do not have to check for possible access violations as we haul stuff on the stack because of the way Windows dynamically grows the stack. It first *reserves* a certain amount of space (normally 1 MB) for the stack and *commits* a lesser amount, normally one page (4 KB) at the top of the stack (recall that the stack grows downwards on x86). That is, there is 1 MB of contiguous address space available for use by the stack, but only a few kilobytes are initially committed (allocated); at the bottom of the allocated memory, there is a *guard page*. As things get pushed on the stack and it grows, eventually the guard page is accessed, which raises an exception and causes the program to allocate another page for the stack---and so on up to the number of reserved pages. Only if you ever run out of reserved pages (for example, due to an infinite recursion) will the program crash with a stack overflow. If that occurs after you have just written under a page of data, then you have a bigger problem than just lack of portability---your stack is almost full, and you need to relink the program with a switch to reserve more memory for the stack. By the way, if the total size of the local variables used by the function exceeds one page, MSVC will insert a *stack probe* in the beginning of your function to make sure there is enough stack space committed before any local variables are accessed (as there is no guarantee that they'll be accessed in order). If you ever disassembled such functions, you might have seen an interesting call named _alloca_probe(); that's what it's for.
So it is relatively safe to read or write the stack below the current ESP value (i.e. beyond the edge of valid data), as long as it is done incrementally---in no more than page-sized (4 KB) increments. On the other hand, catching access violations while accessing the stack is strongly discouraged, because doing so may prevent the default exception handler from allocating more memory for the stack!
But even if we forsake portability and consider just x86 architecture, this approach too just works around the problem without addressing the root cause, much as the original va_pass. The root cause lies in the way variadic functions themselves are implemented by major C/C++ compilers. If you browse through shdarg.h header, you'll notice that among va_list, va_arg, va_start and other va_crap, one thing that is blatantly missing (besides your va_pass) is va_size. The problem is that there is no reliable (let alone portable) way, from within variadic function, to tell the total size of the unnamed arguments it received on the stack---and thus, no good way to copy these arguments for the next variadic function to use. The calling conventions used provide no way to infer the size of the argument list received---nothing short of examining machine code at the return address, or perhaps analyzing stack frames (whose use by the compiler is optional to begin with). Only the *caller* of the variadic function knows the size, because it knows exactly how many arguments it pushed:
my_printf("%lf %c %ld", 4.7, 'A', -17);
push -17
push 65
sub esp, 8
fstp qword ptr[esp]
push offset format ; Pass the format string pointer (4 bytes)
call my_printf
...
add esp, 20
I don't know why C/C++ compilers use such a horrible implementation for variadic functions; more than anything, the reasons are probably historical---people have DLLs, people have libs, and they expect these to work with future versions of the compilers. Imagine MSVC changing the calling convention for variadic calls, and then all your fprintf()'s break (not like that ever stopped Microsoft, mind you).
But regardless, I guess the bottom line is that the only reliable way to wrap a variadic call into another variadic call in C is to use variadic macros instead of variadic functions:
#define my_printf(...) { \
\
printf(__VA_ARGS__); \
\
}
#define my_printf(...) ( \
printf(__VA_ARGS__), \
)
Of course, as with all macros, you are limited to either returning no value (in the first case) or declaring no variables (in the second case), unless you use e.g. proprietary GCC extensions which allow you to have void-valued expressions in a comma-separated list; or if you use C++, you may get inventive with templates.
|
|
|
|
|
Clever, but not portable and potentially dangerous, as peterchen points out.
|
|
|
|
|
|
take a look at 'SVaPassNext<50> svapassnext;' 50 is count of SVaPassNext structures recursively nested.
This is kind of compile time type size manipulation. SVaPassNext contains one DWORD i.e. 50*sizeof(DWORD) is the maximum size of data you can pass thru. If you want more - just change 50 to whatever you want.
"7 You shall have no other gods before me. 8 You shall not make an images in order to bow down to them or serve them. 11 You shall not take the name of the LORD your God in vain. 12 Observe the sabbath day 16 Honor your father and your mother, that your days may be prolonged. 17 You shall not kill. 18 Neither shall you commit adultery. 19 Neither shall you steal. 20 Neither shall you bear false witness against your neighbor. 21 Neither shall you covet anything that's your neighbor's." Your God
|
|
|
|
|
How arguments are passed to ellipis functions is implementation defined and varies between compilers. Specifically, it is not defined whether your 50 bytes "dummy stack" is passed by value, or by reference. Additionally, 50 bytes may be not enough, or to much (which, in a very ugly scenario, may run you into an access violation).
House rule: Always provide a a va_list version for all ellipsed functions. period.
Pandoras Gift #44: Hope. The one that keeps you on suffering. aber.. "Wie gesagt, der Scheiss is' Therapie" boost your code || Fold With Us! || sighist | doxygen
|
|
|
|
|
Look up first at top of this article. This marked as "VS Dev". Next point is that values must be pushed to stack "as is" not as reference because va_arg assumes that. And this would lead to broken logic if there was no distinction between variable passed by value and variable passed by pointer. If you know pointer, doesn't differs from reference for compiled code. So, the only thin place in this approach is that we can't get size of stack at compile time in order to make structure of proper size. But I assure you, call to function of such type like TRACE (it takes only digits and pointers) that overflows 50 DWORDS, looks like TRACE("try write in a sane format here", a,b,c,d,e,f,g...,x,y,z,a1,b1...,x1,y1,z1); If you write such code you can change 50 to 100 to satisfy needs. Access violation indeed can have place but only if memory right after stack is marked as NOACCESS. But I suppose, probability of such occasion is VERY low. Because stack must grow. And for this bad case we can use try{}catch{} around memcpy. This is what I'm going to do. Thank you for your criticism.
"7 You shall have no other gods before me. 8 You shall not make an images in order to bow down to them or serve them. 11 You shall not take the name of the LORD your God in vain. 12 Observe the sabbath day 16 Honor your father and your mother, that your days may be prolonged. 17 You shall not kill. 18 Neither shall you commit adultery. 19 Neither shall you steal. 20 Neither shall you bear false witness against your neighbor. 21 Neither shall you covet anything that's your neighbor's." Your God
|
|
|
|
|
I just wanted to point out that you are relying on compiler-specific behavior, and that other compilers in fact do implement this differently.
Yes, this works in most cases with VC compilers. Yes, the chances of breaking are very low. But I've seen more exotic things break at customer sites only, and your code breaks silently.
It's a neat trick, but I wouldn't allow it in production code. After all you are saving what? 2 lines of code, plus two braces
void Foo(LPCTSTR x, ...)
{
va_list varg;
va_start(varg, x);
....
va_end(varg);
} vs.
void FooV(LPCTSTR x, va_list varg)
{
....
}
void Foo(LPCTSTR x, ...)
{
va_list varg;
va_start(varg, x);
FooV(x,varg);
va_end(varg);
}
Pandoras Gift #44: Hope. The one that keeps you on suffering. aber.. "Wie gesagt, der Scheiss is' Therapie" boost your code || Fold With Us! || sighist | doxygen
|
|
|
|
|
I just tried this on GCC and this work! So let us try this method in Borland С++ and all other compilers and we'll see. It's very interesting. The way you suggest is the common work around of problem. I suggest to solve problem. What will you do when there's no FooV? You will write it? That what I'm saving. Now my code cannot lead to crush (I've suppled the try-catch scope, thank you!). So, I just read memory. If exception is rised I stop reading. But what I had read will be enough by definition.
"7 You shall have no other gods before me. 8 You shall not make an images in order to bow down to them or serve them. 11 You shall not take the name of the LORD your God in vain. 12 Observe the sabbath day 16 Honor your father and your mother, that your days may be prolonged. 17 You shall not kill. 18 Neither shall you commit adultery. 19 Neither shall you steal. 20 Neither shall you bear false witness against your neighbor. 21 Neither shall you covet anything that's your neighbor's." Your God
|
|
|
|
|
Well, i'm certainly sure it's not portable. va_list, va_start, va_arg and va_end are compiler and platform independent.
That's why va_copy was introduced with C99: "7.15.1.2 The va_copy macro" in http://www.open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
|
|
|
|
|
But C99 isnt supported on all compilers.
While this might not be portable, does it really matter, alot of people dont write on multiple platforms and work solely in the dev studio/windows enviroment and helping people write less code can only be a good thing.
I think this is a pretty cool trick and people can maybe learn from this and think of different ways to apply it.
One thing i wasnt sure about was why you used a template for the array and not just a block of DWORDS?
|
|
|
|
|
I don't know why, but I have a feeling that memcpy never throws, it may only crash. Even reading out of boundary memory is NOT a good idea: in debug mode it may easily trigger assertion and in release it may segfault
I'm very comfortable with template<templates<> >, member function pointers and other rarely used parts of c++, but I never knew how to use var args , just now needed to implement ATLTRACE() style macro and off course searched codeproject on how to use it
|
|
|
|
|
I don't know linux exception model. But I believe it's quite the same: you can catch exception of reading protected memory. This cannot harm your process in windows at all. When you try to read protected memory, system just notifies about it. So process of copying just stops at boundary of page, if you handle this exception. Woe to you if not . In all cases needful part of stack is being copied and next VA function gets all it needs. I found this approash very useful since this avoids attraction of very specific functions: you can just pass ... further and that all. Cheer up!
"7 You shall have no other gods before me. 8 You shall not make an images in order to bow down to them or serve them. 11 You shall not take the name of the LORD your God in vain. 12 Observe the sabbath day 16 Honor your father and your mother, that your days may be prolonged. 17 You shall not kill. 18 Neither shall you commit adultery. 19 Neither shall you steal. 20 Neither shall you bear false witness against your neighbor. 21 Neither shall you covet anything that's your neighbor's." Your God
|
|
|
|
|