c++ - tener - medidor de meta description
longitud de va_list cuando se usan argumentos de lista de variables? (6)
¿Hay alguna manera de calcular la longitud de va_list
? Todos los ejemplos que vi el número de parámetros variables se da explícitamente.
Hmm, si no tienes miedo de un asm asqueroso, puedes explotar la convención de llamadas de tu compilador. Sin embargo, esto limitará su código a una plataforma específica / compilador / convención de llamada.
Por ejemplo, en la aplicación de Windows BDS2006 C ++ 32bit x86 (solo me referiré a esta plataforma) los argumentos se colocan en la pila y luego se llama el valor del puntero de la pila (por el tamaño de la pila utilizada) después de que se devuelve la función. Aquí un pequeño ejemplo:
double x;
x=min(10.0,20.0,30.0,40.0,50.0);
La llamada se traduce a esto:
Unit1.cpp.28: x=min(10.0,20.0,30.0,40.0,50.0);
00401B9C 6800004940 push $40490000
00401BA1 6A00 push $00
00401BA3 6800004440 push $40440000
00401BA8 6A00 push $00
00401BAA 6800003E40 push $403e0000
00401BAF 6A00 push $00
00401BB1 6800003440 push $40340000
00401BB6 6A00 push $00
00401BB8 6800002440 push $40240000
00401BBD 6A00 push $00
00401BBF E894FDFFFF call min(double,double,????)
00401BC4 83C428 add esp,$28
Preste atención a la última instrucción después de la llamada. El $28
es el tamaño consumido por 4 argumentos y un valor de retorno. Entonces, si puede leer ese valor en su función, puede determinar exactamente el número de argumentos (si se conoce su tamaño). Así que aquí ejemplo de trabajo:
double min(double x,double ...) // = min(x,y)
{
int n,dn=sizeof(double);
asm {
mov eax,esp // store original stack pointer
mov esp,ebp // get to the parrent scope stack pointer
pop ebx
pop ebx // this reads the return address of the call pointing to the first instruction after it which is what we want
mov esp,eax // restore stack pointer
sub eax,eax; // just eax=0
mov al,[ebx+2] // read lowest BYTE of eax with the $28 from the add esp,$28
mov n,eax // store result to local variable for usage
}
n-=dn; // remove return value from the count
double z; z=x;
va_list va;
va_start(va,x); n-=dn;
for (;n>=0;n-=dn)
{
x=va_arg(va,double);
if (z>x) z=x;
}
va_end(va);
return z;
}
Tenga en cuenta que cada compilador puede tener una secuencia de llamada diferente, así que primero compruebe la lista de ensamblajes, ¡¡¡antes de usar el debug !!!
No hay una forma directa para que una función variad determine cuántos argumentos se pasaron. (Al menos no hay una forma portátil; la interfaz <stdarg.h>
no proporciona esa información).
Hay varias formas indirectas .
Dos de las más comunes son:
- Una cadena de formato (que especifica, a través de lo que podría llamar un lenguaje pequeño y simple, el número y el tipo (s) de los argumentos restantes). Las familias de funciones
*printf()
y*scanf()
usan este mecanismo. - Un valor centinela que denota el final de los argumentos. Algunas de la familia de funciones Unix / POSIX
exec*()
hacen esto, utilizando un puntero nulo para marcar el final de los argumentos.
Pero hay otras posibilidades:
- Más simplemente, un recuento de enteros que especifica el número de argumentos siguientes; Es de suponer que en este caso todos serían del mismo tipo.
- Argumentos alternativos, donde un argumento puede ser un valor de enumeración que especifica el tipo del siguiente argumento. Un ejemplo hipotético podría verse como:
func(ARG_INT, 42, ARG_STRING, "foo", ARG_DOUBLE, 1.25, ARG_END);
o incluso:
func("-i", 42, "-s", "foo", "-d", 1.25, "");
si desea emular la forma en que los argumentos se pasan normalmente a los comandos de Unix.
Incluso podría asignar un valor a una variable global para especificar el número de argumentos:
func_arg_count = 3;
func(1, 2, 3);
Lo que sería feo pero perfectamente legal.
En todas estas técnicas, es responsabilidad de la persona que llama pasar argumentos consistentes; el destinatario solo puede asumir que sus parámetros son correctos.
Tenga en cuenta que no se requiere una función variadic para procesar todos los argumentos que se le pasaron. Por ejemplo, esto:
printf("%d/n", 10, 20);
Imprimirá 10
e ignorará silenciosamente el 20
. Rara vez hay alguna razón para tomar ventaja de esa característica.
No hay forma de calcular la longitud de un va_list
, por eso necesita la cadena de formato en las funciones similares a printf
.
Las únicas macros de funciones disponibles para trabajar con un va_list
son :
-
va_start
- comienza a usar la listava_list
-
va_arg
- obtener el siguiente argumento -
va_end
- deja de usar la listava_list
-
va_copy
(desde C ++ 11 y C99) - copia la listava_list
Tenga en cuenta que debe llamar a va_start
y va_end
en el mismo ámbito, lo que significa que no puede envolverlo en una clase de utilidad que llame a va_start
en su constructor y va_end
en su destructor (una vez me mordió este).
Por ejemplo, esta clase no vale nada:
class arg_list {
va_list vl;
public:
arg_list(const int& n) { va_start(vl, n); }
~arg_list() { va_end(vl); }
int arg() {
return static_cast<int>(va_arg(vl, int);
}
};
GCC produce el siguiente error
t.cpp: En el constructor
arg_list::arg_list(const int&)
:
Línea 7: error:va_start
utilizado en la función conva_start
fijos
Compilación terminada debido a errores -Fatal.
Puede intentar usar la función _vscprintf
si trabaja en MS Visual Studio. Aquí hay un ejemplo de cómo usar _vscprintf, lo usé para saber cuánto espacio necesito para malloc para el título de mi consola.
int SetTitle(const char *format,...){
char *string;
va_list arguments;
va_start(arguments,format);
string=(char *)malloc(sizeof(char)*(_vscprintf(format,arguments)+1));
if(string==NULL)
SetConsoleTitle("Untitled");
else
vsprintf(string,format,arguments);
va_end(arguments);
if(string==NULL)
return SETTITLE_MALLOCFAILED;
SetConsoleTitle(string);
free(string);
return 0;
}
O puede hacer esto, agregar una salida a un archivo temporal y luego leer los datos del mismo en la memoria asignada como lo hice en el siguiente ejemplo:
void r_text(const char *format, ...){
FILE *tmp = tmpfile();
va_list vl;
int len;
char *str;
va_start(vl, format);
len = vfprintf(tmp, format, vl);
va_end(vl);
rewind(tmp);
str = (char *) malloc(sizeof(char) * len +1);
fgets(str, len+1, tmp);
printf("%s",str);
free(str);
fclose(tmp);
}
Un enfoque que aún no se ha mencionado es usar una macro de preprocesador para llamar a la función variadict usando la longitud va_list como primer parámetro y también reenviar a lo largo de los argumentos. Esto es algo así como una solución "linda", pero no requiere ingresar manualmente la longitud de la lista de argumentos.
Supongamos que tiene la siguiente función:
int Min(int count, ...) {
va_list args;
va_start(args, count);
int min = va_arg(args, int);
for (int i = 0; i < count-1; ++i) {
int next = va_arg(args, int);
min = min < next ? min : next;
}
va_end(args);
return min;
}
La idea es que tenga una macro de preprocesador capaz de contar el número de argumentos utilizando una máscara para __VA_ARGS__
. Hay algunas buenas bibliotecas de preprocesadores para determinar la longitud de __VA_ARGS__
incluidas P99 y Boost Preprocessor, pero solo para no dejar huecos en esta respuesta, así es como se puede hacer:
#define IS_MSVC _MSC_VER && !__INTEL_COMPILER
/**
* Define the macros to determine variadic argument lengths up to 20 arguments. The MSVC
* preprocessor handles variadic arguments a bit differently than the GNU preprocessor,
* so we account for that here.
*/
#if IS_MSVC
#define MSVC_HACK(FUNC, ARGS) FUNC ARGS
#define APPLY(FUNC, ...) MSVC_HACK(FUNC, (__VA_ARGS__))
#define VA_LENGTH(...) APPLY(VA_LENGTH_, 0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#else
#define VA_LENGTH(...) VA_LENGTH_(0, ## __VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#endif
/**
* Strip the processed arguments to a length variable.
*/
#define VA_LENGTH_(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N
Nota : Gran parte del ruido de arriba es el soporte para MSVC.
Con lo definido anteriormente, puede crear una sola macro para realizar todas las operaciones basadas en longitud:
/**
* Use the VA_LENGTH macro to determine the length of the variadict args to
* pass in as the first parameter, and forward along the arguments after that.
*/
#define ExecVF(Func, ...) Func(VA_LENGTH(__VA_ARGS__), __VA_ARGS__)
Esta macro es capaz de llamar a cualquier función variadict siempre que comience con el parámetro int count
. En resumen, en lugar de utilizar:
int result = Min(5, 1, 2, 3, 4, 5);
Puedes usar:
int result = ExecVF(Min, 1, 2, 3, 4, 5);
Aquí hay una versión de plantilla de Min que utiliza el mismo enfoque: https://gist.github.com/mbolt35/4e60da5aaec94dcd39ca
Utilice _vscprintf para determinar la longitud de la lista de variables. https://msdn.microsoft.com/en-us/library/w05tbk72.aspx