CallVoidMethod
- is variable, i.e. in addition to the pointer to the JNI environment, the pointer to the type of the object being created, and the identifier of the method being called (in this case, the constructor), it takes an arbitrary number of other arguments. Which is logical, because these other arguments are passed to the called method on the Java side, and the methods may be different, with different numbers of arguments of any type.CallVoidMethod
used va_list
, because there is no other way. Yes, and sent va_list
to CallVoidMethod
. And dropped the JVM with the commonplace segmentation fault.CallVoidMethod
there is CallVoidMethodV
, which takes an arbitrary number of arguments via va_list
.va_list
. But noticing, I could not explain to myself what the fundamental difference is. This means that it is necessary to deal with both ellipse, and va_list
, and (since it’s still C ++) with variable patterns. void foo(int parm1, int parm2, ...);
parm2
in the example above) [C11 6.7.6.3/9] .va_list
type and 4 (3 to C11 standard) macros in the <stdarg.h>
header: va_start
, va_arg
, va_end
and va_copy
(starting with C11) [C11 7.16] . int add(int count, ...) { int result = 0; va_list args; va_start(args, count); for (int i = 0; i < count; ++i) { result += va_arg(args, int); } va_end(args); return result; }
NULL
as the last argument, as in execl
, or 0).register
storage class, cannot be a function or an array. Otherwise, the behavior is undefined [C11 7.16.1.4/4] .char
, short
(with or without a sign) or float
, then the corresponding parameters should be treated as int
, int
(with or without a sign) or double
. Otherwise, the behavior is undefined [C11 7.16.1.1/2] .va_list
says only that it is declared in <stdarg.h>
and is complete (that is, the size of an object of this type is known) [C11 7.16 / 3] .va_list
declared in the Standard, but nothing is said about its internal structure?va_list
? It could be said now: “as syntactic sugar,” but 40 years ago, I am sure, it was not up to sugar.By the way, there is no acceptable way to write a portable function of an arbitrary number of arguments, since There is no portable way for the called function to find out how many arguments were passed to it when it was called. ... printf
, the most typical C language function of an arbitrary number of arguments ... is not portable and must be implemented for each system.
This book describes printf
, but no vprintf
yet, and does not mention the type and macros va_*
. They appear in the second edition of the C Programming Language (1988), and this is the merit of the committee to develop the first C Standard (C89, aka ANSI C). The committee added the heading <stdarg.h>
to the Standard, based on <varargs.h>
, created by Andrew Koenig ( Andrew Koenig ) in order to increase the portability of the UNIX OS. va_*
was decided to leave the va_*
macros as macros to make it easier for existing compilers to support the new Standard.va_*
family, it has become possible to create portable variable functions. And although the internal structure of this family is still not described in any way, and there are no requirements for it, it is already clear why.<stdarg.h>
. For example, in the same Standard Library C, an example is given for Borland Turbo C ++ : #ifndef _STADARG #define _STADARG #define _AUPBND 1 #define _ADNBND 1 typedef char* va_list #define va_arg(ap, T) \ (*(T*)(((ap) += _Bnd(T, _AUPBND)) - _Bnd(T, _ADNBND))) #define va_end(ap) \ (void)0 #define va_start(ap, A) \ (void)((ap) = (char*)&(A) + _Bnd(A, _AUPBND)) #define _Bnd(X, bnd) \ (sizeof(X) + (bnd) & ~(bnd)) #endif
va_list
: typedef struct { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; } va_list[1];
va_*
provide a standard interface for traversing the arguments of a variable function, and their implementation for historical reasons depends on the compiler, the target platform and the architecture. Moreover, an ellipse (i.e., variable functions in general) appeared in C earlier than va_list
(i.e., the header <stdarg.h>
). And va_list
was created not to replace the ellipsis, but to allow developers to write their own portable variable functions.va_*
:register
variable, then the last named argument of a variable function can have this storage class.parmN
is not the case.
need to be replaced byparmN
is not the case of the parameter.
std::nullptr_t
to void*
and allowed compilers to support types with non-trivial constructors and destructors at their discretion [C ++ 11 5.2.2 / 7] . C ++ 14 allowed to use functions and arrays as [C ++ 14 18.10 / 3] as the last named parameter, and C ++ 17 forbade disclosing of package of parameters ( pack expansion ) and variables captured by lambda [C ++ 17 21.10.1 / 1] .char
, signed char
, unsigned char
, singed short
, unsigned short
or float
. The result according to the Standard will be undefined behavior. void foo(float n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; va_end(va); }
./test.cpp:7:18: warning: passing an object that undergoes default argument promotion to 'va_start' has undefined behavior [-Wvarargs] va_start(va, n); ^
void foo(double n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; va_end(va); }
void foo(int& n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; va_end(va); }
./test.cpp:7:18: warning: passing an object of reference type to 'va_start' has undefined behavior [-Wvarargs] va_start(va, n); ^
va_start
argument va_start
not be a link. c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.15.26726\include\vadefs.h(151): error C2338: va_start argument must not have reference type and must not be parenthesized
void foo(int* n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; va_end(va); }
va_arg
type to be va_arg
- char
, short
or float
. #include <cstdarg> #include <iostream> void foo(int n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; std::cout << va_arg(va, float) << std::endl; std::cout << va_arg(va, int) << std::endl; va_end(va); } int main() { foo(0, 1, 2.0f, 3); return 0; }
double
instead of float
, and if this code is still executed, the program will end with an error. ./test.cpp:9:15: warning: 'float' is promoted to 'double' when passed through '...' std::cout << va_arg(va, float) << std::endl; ^~~~~~ ./test.cpp:9:15: note: (so you should pass 'double' not 'float' to 'va_arg') ./test.cpp:9:15: note: if this code is reached, the program will abort
va_list
. For 32 bits this va = 0xfffc6918 ""
va_list
is just char*
. For 64 bits: va = {{gp_offset = 16, fp_offset = 48, overflow_arg_area = 0x7ffef147e7e0, reg_save_area = 0x7ffef147e720}}
float
with double
. ./test.cpp:9:26: warning: second argument to 'va_arg' is of promotable type 'float'; this va_arg has undefined behavior because arguments will be promoted to 'double' [-Wvarargs] std::cout << va_arg(va, float) << std::endl; ^~~~~
1 0 1073741824
1 0 3
/Wall
.foo
not with 4 arguments, but from 19, and also output them, the result will be the same: full mash in the 32-bit version, and zeros for all float
in the 64-bit. Those. It's a matter of course in ABI, but not in the use of registers for passing arguments. void foo(int n, ...) { va_list va; va_start(va, n); std::cout << va_arg(va, int) << std::endl; std::cout << va_arg(va, double) << std::endl; std::cout << va_arg(va, int) << std::endl; va_end(va); }
#include <cstdarg> #include <iostream> struct Bar { Bar() { std::cout << "Bar default ctor" << std::endl; } Bar(const Bar&) { std::cout << "Bar copy ctor" << std::endl; } ~Bar() { std::cout << "Bar dtor" << std::endl; } }; struct Cafe { Cafe() { std::cout << "Cafe default ctor" << std::endl; } Cafe(const Cafe&) { std::cout << "Cafe copy ctor" << std::endl; } ~Cafe() { std::cout << "Cafe dtor" << std::endl; } }; void foo(int n, ...) { va_list va; va_start(va, n); std::cout << "Before va_arg" << std::endl; const auto b = va_arg(va, Bar); va_end(va); } int main() { Bar b; Cafe c; foo(1, b, c); return 0; }
va_arg
not a POD type, and warns that the program will va_arg
when launched. ./test.cpp:23:31: error: second argument to 'va_arg' is of non-POD type 'Bar' [-Wnon-pod-varargs] const auto b = va_arg(va, Bar); ^~~ ./test.cpp:31:12: error: cannot pass object of non-trivial type 'Bar' through variadic function; call will abort at runtime [-Wnon-pod-varargs] foo(1, b, c); ^
-Wno-non-pod-varargs
flag. d:\my documents\visual studio 2017\projects\test\test\main.cpp(31): warning C4840: "Bar"
Bar default ctor Cafe default ctor Before va_arg Bar copy ctor Bar dtor Cafe dtor Bar dtor
va_arg
, and the argument, it turns out, is passed by reference. Somehow not obvious, but the Standard allows. Bar default ctor Cafe default ctor Before va_arg Bar copy ctor Bar dtor Cafe dtor Bar dtor
Bar default ctor Cafe default ctor Cafe copy ctor Bar copy ctor Before va_arg Bar copy ctor Bar dtor Bar dtor Cafe dtor Cafe dtor Bar dtor
va_arg
it makes another copy. It would be fun to look for this difference when switching from the 6th to the 7th version of gcc, if designers / destructors have side effects. void foo(int n, ...) { va_list va; va_start(va, n); std::cout << "Before va_arg" << std::endl; const auto& b = va_arg(va, Bar&); va_end(va); } int main() { Bar b; Cafe c; foo(1, std::ref(b), c); return 0; }
void foo(int n, ...) { va_list va; va_start(va, n); std::cout << "Before va_arg" << std::endl; const auto* b = va_arg(va, Bar*); va_end(va); } int main() { Bar b; Cafe c; foo(1, &b, &c); return 0; }
#include <iostream> void foo(...) { std::cout << "C variadic function" << std::endl; } void foo(int) { std::cout << "Ordinary function" << std::endl; } int main() { foo(1); foo(1ul); foo(); return 0; }
$ ./test Ordinary function Ordinary function C variadic function
foo
without arguments is not considered separately. #include <iostream> void foo(...) { std::cout << "C variadic function" << std::endl; } void foo() { std::cout << "Ordinary function without arguments" << std::endl; } int main() { foo(1); foo(); return 0; }
./test.cpp:16:9: error: call of overloaded 'foo()' is ambiguous foo(); ^ ./test.cpp:3:6: note: candidate: void foo(...) void foo(...) ^~~ ./test.cpp:8:6: note: candidate: void foo() void foo() ^~~
template <class T> struct HasFoo { private: template <class U, class = decltype(std::declval<U>().foo())> static void detect(const U&); static int detect(...); public: static constexpr bool value = std::is_same<void, decltype(detect(std::declval<T>()))>::value; };
template <class T> struct HasFoo { private: template <class U, class = decltype(std::declval<U>().foo())> static constexpr bool detect(const U*) { return true; } template <class U> static constexpr bool detect(...) { return false; } public: static constexpr bool value = detect<T>(nullptr); };
detect(...)
. I would prefer to change a couple of lines and use a modern alternative to variable functions, devoid of all their flaws. template <class ... Args> void foo(const std::string& format, Args ... args) { printf(format.c_str(), args...); }
class ... Args
— , Args ... args
— , args...
— . template <class ... Args> void bar(const std::string& format, Args ... args) { foo<Args...>(format.c_str(), args...); }
template <class ... Args> void foo(const std::string& format, Args ... args) { const auto list = {args...}; }
template <class ... Args> void foo(const std::string& format, Args ... args) { auto lambda = [&format, args...] () { printf(format.c_str(), args...); }; lambda(); }
template <class ... Args> int foo(Args ... args) { return (0 + ... + args); }
template <class ... Args> void foo(Args ... args) { const auto size1 = sizeof...(Args); const auto size2 = sizeof...(args); }
template <class ... Args> void foo() { using OneTuple = std::tuple<std::tuple<Args>...>; using NestTuple = std::tuple<std::tuple<Args...>>; }
OneTuple
— ( std:tuple<std::tuple<int>>, std::tuple<double>>
), NestTuple
— , — ( std::tuple<std::tuple<int, double>>
).printf
- one of the first variable functions in C. void printf(const char* s) { while (*s) { if (*s == '%' && *++s != '%') throw std::runtime_error("invalid format string: missing arguments"); std::cout << *s++; } } template <typename T, typename ... Args> void printf(const char* s, T value, Args ... args) { while (*s) { if (*s == '%' && *++s != '%') { std::cout << value; return printf(++s, args...); } std::cout << *s++; } throw std::runtime_error("extra arguments provided to printf"); }
template <typename ... Args> void printf(const std::string& fmt, const Args& ... args) { size_t fmtIndex = 0; size_t placeHolders = 0; auto printFmt = [&fmt, &fmtIndex, &placeHolders]() { for (; fmtIndex < fmt.size(); ++fmtIndex) { if (fmt[fmtIndex] != '%') std::cout << fmt[fmtIndex]; else if (++fmtIndex < fmt.size()) { if (fmt[fmtIndex] == '%') std::cout << '%'; else { ++fmtIndex; ++placeHolders; break; } } } }; ((printFmt(), std::cout << args), ..., (printFmt())); if (placeHolders < sizeof...(args)) throw std::runtime_error("extra arguments provided to printf"); if (placeHolders > sizeof...(args)) throw std::runtime_error("invalid format string: missing arguments"); }
#include <iostream> void foo(int) { std::cout << "Ordinary function" << std::endl; } void foo() { std::cout << "Ordinary function without arguments" << std::endl; } template <class T> void foo(T) { std::cout << "Template function" << std::endl; } template <class ... Args> void foo(Args ...) { std::cout << "Template variadic function" << std::endl; } int main() { foo(1); foo(); foo(2.0); foo(1, 2); return 0; }
$ ./test Ordinary function Ordinary function without arguments Template function Template variadic function
#include <iostream> void foo(...) { std::cout << "C variadic function" << std::endl; } template <class ... Args> void foo(Args ...) { std::cout << "Template variadic function" << std::endl; } int main() { foo(1); foo(); return 0; }
$ ./test Template variadic function C variadic function
std::initializer_list
). And although this contradicted the theoretical rationale of the author himself, Joly proposed to implement it std::min
, std::max
and std::minmax
it was with the help of initialization lists, and not variable templates.std::min
, std::max
and std::minmax
are the usual template functions, an arbitrary number of arguments are passed through the initialization list.Source: https://habr.com/ru/post/430064/
All Articles