Funciones con Argumentos Variables (Variadic Functions)¶
Introducción¶
Las funciones con argumentos variables permiten pasar una cantidad indeterminada de parámetros a una función. Esto es posible gracias a macros especiales definidas en <cstdarg> (C++) o <stdarg.h> (C).
Declaración¶
Ejemplo:
- Los parámetros fijos (
a,c) siempre están presentes - Los tres puntos suspensivos (
...) indican argumentos variables adicionales
Componentes Principales¶
1. Tipo va_list¶
- Es un tipo de dato que funciona como un puntero
- A bajo nivel es equivalente a
char*ovoid* - Guarda la dirección de un array con los argumentos variables
2. Macro va_start¶
Función:
- Inicializa ap con la dirección del primer argumento variable
- Requiere dos parámetros:
- ap: variable del tipo va_list
- last_fixed_param: último parámetro conocido/fijo
Ejemplo:
3. Macro va_arg¶
Función: - Devuelve el siguiente valor de la lista de parámetros - Requiere especificar el tipo del argumento - Incrementa automáticamente el puntero al siguiente argumento
Comportamiento:
1. Lee el valor actual apuntado por ap
2. Incrementa ap para apuntar al siguiente
3. Devuelve el valor según el tipo especificado
4. Macro va_end¶
Función:
- Restaura el estado de ap
- Debe llamarse antes de retornar de la función
Convención de Llamada x64¶
Paso de Argumentos¶
En arquitectura x64, los argumentos se pasan en el siguiente orden:
- Primeros 4 argumentos: Registros
- 1º →
RCX - 2º →
RDX - 3º →
R8 -
4º →
R9 -
Argumentos 5+: Stack (pila)
- Se colocan debajo del shadow space
Shadow Space¶
+------------------+ <- RSP al inicio de función
| Shadow Space | 16 bytes (4 slots de 8 bytes)
| (4 registros) |
+------------------+
| Arg #1 (RCX) | [RSP+8]
| Arg #2 (RDX) | [RSP+16]
| Arg #3 (R8) | [RSP+24]
| Arg #4 (R9) | [RSP+32]
+------------------+
| Arg #5 | [RSP+40]
| Arg #6 | [RSP+48]
| Arg #7 | [RSP+56]
| ... |
+------------------+
Importante: Aunque los primeros 4 argumentos se pasen por registro, se copian al stack en el shadow space, permitiendo tratarlos como un array continuo.
Ejemplo 1: Tipos Homogéneos¶
Código Fuente¶
#include <iostream>
#include <cstdarg>
using namespace std;
void funcion(int a, ...);
int main() {
funcion(1, "cadena 1", 0);
funcion(1, "cadena 1", "cadena 2", "cadena 3", "cadena4", "cadena5", 0);
funcion(2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0);
return 0;
}
void funcion(int a, ...) {
va_list p;
va_start(p, a);
char* arg;
int arg2;
if (a == 1) {
while ((arg = va_arg(p, char*))) {
cout << arg << " ";
}
}
else {
while ((arg2 = va_arg(p, int))) {
cout << arg2 << " ";
}
}
va_end(p);
cout << endl;
}
Análisis del Comportamiento¶
Primera llamada: funcion(1, "cadena 1", 0)¶
a = 1→ Usa tipochar*(strings)va_start(p, a)→papunta a"cadena 1"- Primera iteración:
va_arg(p, char*)devuelve"cadena 1"pse incrementa para apuntar a0- Imprime:
"cadena 1" - Segunda iteración:
va_arg(p, char*)devuelve0- El
whiletermina (condición falsa)
Segunda llamada: funcion(1, "cadena 1", ..., "cadena5", 0)¶
- Itera 5 veces imprimiendo todas las cadenas
- Se detiene al encontrar
0
Tercera llamada: funcion(2, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0)¶
a = 2→ Usa tipoint- Itera imprimiendo los enteros hasta encontrar
0
Análisis a Bajo Nivel¶
Alineación en Stack¶
Después de guardar los registros en shadow space:
[RSP+8] → Arg #1 (tipo: 1 o 2)
[RSP+16] → Arg #2 (primer argumento variable)
[RSP+24] → Arg #3
[RSP+32] → Arg #4
[RSP+40] → Arg #5
[RSP+48] → Arg #6
[RSP+56] → Arg #7
...
Operación va_start¶
lea rax, QWORD PTR [rsp+16] ; Dirección del 2º argumento
mov QWORD PTR p$[rsp], rax ; p = dirección del array de args variables
Resultado: p apunta al primer argumento variable (el siguiente después de a)
Operación va_arg¶
; Incrementar puntero
mov rax, QWORD PTR p$[rsp] ; Cargar p
add rax, 8 ; p += 8 (siguiente elemento)
mov QWORD PTR p$[rsp], rax ; Guardar p incrementado
; Leer valor anterior
mov rax, QWORD PTR p$[rsp] ; Cargar p (ya incrementado)
mov rax, QWORD PTR [rax-8] ; Leer valor en p-8 (elemento actual)
Mecanismo:
1. Incrementa p en 8 bytes (tamaño de un QWORD)
2. Lee el valor en p - 8 (el elemento que acabamos de pasar)
3. Esto permite avanzar al siguiente elemento mientras se devuelve el actual
Tipos char* vs int¶
Para char* (strings):
mov rax, QWORD PTR [rax-8] ; Leer dirección (8 bytes)
mov rax, QWORD PTR [rax] ; Dereferencia adicional (opcional)
Para int:
Condición de Salida¶
Importante: Por esto es crítico terminar con 0 los argumentos variables.
Ejemplo 2: Tipos Mixtos¶
Código Fuente¶
#include <iostream>
#include <cstdarg>
using namespace std;
void funcion(int a, ...);
int main() {
funcion(1, "cadena 1", 2, 9, "cadena 2", "cadena 3", "cadena4", "cadena5", 0);
return 0;
}
void funcion(int a, ...) {
va_list p;
va_start(p, a);
for (int i = 0; i < 10; i++) {
if (i == 0 || i == 3 || i == 4 || i == 5 || i == 6) {
cout << "Cadena: ";
cout << va_arg(p, char*) << " " << endl;
}
else {
cout << "Entero: ";
cout << va_arg(p, int) << " " << endl;
}
}
va_end(p);
cout << endl;
}
Análisis¶
Secuencia de Argumentos¶
| Índice | Posición | Tipo | Valor |
|---|---|---|---|
| 0 | Arg #2 | char* |
"cadena 1" |
| 1 | Arg #3 | int |
2 |
| 2 | Arg #4 | int |
9 |
| 3 | Arg #5 | char* |
"cadena 2" |
| 4 | Arg #6 | char* |
"cadena 3" |
| 5 | Arg #7 | char* |
"cadena4" |
| 6 | Arg #8 | char* |
"cadena5" |
| 7 | Arg #9 | int |
0 |
Manejo de Tipos Mixtos¶
El código debe conocer explícitamente el tipo de cada argumento:
if (i == 0 || i == 3 || i == 4 || i == 5 || i == 6) {
// Posiciones con strings
va_arg(p, char*)
}
else {
// Posiciones con enteros
va_arg(p, int)
}
Análisis a Bajo Nivel¶
Alineación en Stack¶
[RSP+8] → 1 (tipo, parámetro fijo)
[RSP+16] → "cadena 1" (char*, 8 bytes)
[RSP+24] → 2 (int, ocupa 8 bytes en stack)
[RSP+32] → 9 (int, ocupa 8 bytes en stack)
[RSP+40] → "cadena 2" (char*, 8 bytes)
[RSP+48] → "cadena 3" (char*, 8 bytes)
[RSP+56] → "cadena4" (char*, 8 bytes)
[RSP+64] → "cadena5" (char*, 8 bytes)
[RSP+72] → 0 (int, ocupa 8 bytes en stack)
Nota: Aunque int ocupa 4 bytes, cada slot en el stack es de 8 bytes (QWORD).
Verificación de Tipo en Ensamblador¶
; Comparaciones para determinar el tipo
cmp DWORD PTR i$1[rsp], 0
je SHORT $LN7@funcion ; Si i==0, es string
cmp DWORD PTR i$1[rsp], 3
je SHORT $LN7@funcion ; Si i==3, es string
cmp DWORD PTR i$1[rsp], 4
je SHORT $LN7@funcion ; Si i==4, es string
; ... más comparaciones
jne $LN5@funcion ; Si no coincide, es entero
Consideraciones Importantes¶
1. Responsabilidad del Programador¶
El programador debe:
- Conocer el tipo de cada argumento
- Especificar correctamente el tipo en va_arg
- Terminar la lista con un valor centinela (generalmente 0)
2. Peligros de Tipos Incorrectos¶
Si se especifica un tipo incorrecto:
Consecuencias: - Lectura de datos incorrectos - Posibles accesos a memoria inválida - Crash del programa
3. Centinela 0¶
Siempre terminar con 0:
Razón: Sin centinela, el loop podría: - Leer basura en el stack - Interpretar datos aleatorios como argumentos válidos - Causar comportamiento indefinido
4. Limitaciones¶
- No hay verificación de tipos en tiempo de compilación
- No se puede saber la cantidad de argumentos automáticamente
- Requiere convención entre llamador y función
Formas Alternativas de Manejo¶
Con Cantidad Explícita¶
void funcion(int count, ...) {
va_list p;
va_start(p, count);
for (int i = 0; i < count; i++) {
int valor = va_arg(p, int);
cout << valor << " ";
}
va_end(p);
}
// Uso
funcion(3, 10, 20, 30); // No necesita centinela
Sin While¶
void funcion(int a, ...) {
va_list p;
va_start(p, a);
int arg1 = va_arg(p, int);
int arg2 = va_arg(p, int);
int arg3 = va_arg(p, int);
// Acceso directo a argumentos conocidos
va_end(p);
}
Resumen de Macros¶
| Macro | Propósito | Sintaxis |
|---|---|---|
va_list |
Tipo de dato puntero | va_list ap; |
va_start |
Inicializa el puntero | va_start(ap, last_param); |
va_arg |
Lee y avanza | va_arg(ap, type); |
va_end |
Limpia/restaura | va_end(ap); |
Flujo Completo¶
void ejemplo(int fijo, ...) {
// 1. Declarar va_list
va_list args;
// 2. Inicializar
va_start(args, fijo);
// 3. Procesar argumentos
while (condicion) {
tipo valor = va_arg(args, tipo);
// usar valor
}
// 4. Limpiar
va_end(args);
}
Patrones de Implementación a Bajo Nivel¶
Patrón de Incremento y Lectura¶
; Patrón repetido para cada va_arg:
; 1. Incrementar puntero
mov rax, QWORD PTR p[rsp] ; Cargar p
add rax, 8 ; p += sizeof(arg)
mov QWORD PTR p[rsp], rax ; Guardar p actualizado
; 2. Leer valor anterior
mov rax, QWORD PTR p[rsp] ; Cargar p (ya incrementado)
mov rax, QWORD PTR [rax-8] ; Leer valor en posición anterior
; 3. Usar valor en rax
Optimización del Compilador¶
A veces el compilador genera código más eficiente:
; Versión optimizada
lea rax, [p + 8] ; p_nuevo = p + 8
mov rdx, [p] ; valor = *p
mov p, rax ; p = p_nuevo
Referencias¶
- Microsoft MSVC Documentation
<cstdarg>/<stdarg.h>headers- x64 calling convention
Ejemplo 3: Formato con String de Tipos¶
Concepto¶
Una solución más elegante para manejar tipos mixtos es usar el primer argumento como descriptor de formato, similar a printf().
Código Fuente¶
#include <iostream>
#include <cstdarg>
#include <cstring>
using namespace std;
void funcion(const char* formato, ...);
int main() {
funcion("ciic", "Hola", 12, 34, "Adios");
funcion("ccci", "Uno", "Dos", "Tres", 4);
funcion("i", 1);
return 0;
}
void funcion(const char* formato, ...) {
va_list p;
va_start(p, formato);
char* szarg;
int iarg;
for (int i = 0; i < strlen(formato); i++) {
switch (formato[i]) {
case 'c': // char* (string)
szarg = va_arg(p, char*);
cout << szarg << " ";
break;
case 'i': // int
iarg = va_arg(p, int);
cout << iarg << " ";
break;
// Se pueden agregar más tipos: 'f' para float, 'd' para double, etc.
}
}
va_end(p);
cout << endl;
}
Ventajas¶
- No necesita centinela: Usa
strlen(formato)para saber cuántos argumentos procesar - Autodescriptivo: El formato indica el tipo de cada argumento
- Extensible: Fácil agregar nuevos tipos al
switch - Seguro: Cantidad exacta conocida, no lee basura del stack
Interpretación del Formato¶
| Carácter | Tipo | va_arg |
|---|---|---|
'c' |
char* |
va_arg(p, char*) |
'i' |
int |
va_arg(p, int) |
'f' |
float |
va_arg(p, float) |
'd' |
double |
va_arg(p, double) |
Análisis de las Llamadas¶
Primera llamada: funcion("ciic", "Hola", 12, 34, "Adios")¶
formato = "ciic" (4 caracteres)
Arg #1: "Hola" (c → char*)
Arg #2: 12 (i → int)
Arg #3: 34 (i → int)
Arg #4: "Adios" (c → char*)
Output: Hola 12 34 Adios
Segunda llamada: funcion("ccci", "Uno", "Dos", "Tres", 4)¶
formato = "ccci" (4 caracteres)
Arg #1: "Uno" (c → char*)
Arg #2: "Dos" (c → char*)
Arg #3: "Tres" (c → char*)
Arg #4: 4 (i → int)
Output: Uno Dos Tres 4
Tercera llamada: funcion("i", 1)¶
Análisis a Bajo Nivel¶
Stack Layout - Primera Llamada¶
[RSP+8] → formato: dirección de "ciic"
[RSP+16] → "Hola" (char*, 8 bytes)
[RSP+24] → 12 (int, ocupa 8 bytes en stack)
[RSP+32] → 34 (int, ocupa 8 bytes en stack)
[RSP+40] → "Adios" (char*, 8 bytes)
Flujo del Switch en Ensamblador¶
; Obtener carácter del formato
movsxd rax, DWORD PTR i$[rsp] ; i (índice)
mov rcx, QWORD PTR formato$[rsp] ; Dirección de formato
movzx eax, BYTE PTR [rcx+rax] ; formato[i]
mov BYTE PTR char[rsp], al ; Guardar carácter
; Comparar con 'c' (0x63)
cmp BYTE PTR char[rsp], 99 ; 0x63 = 'c'
je SHORT $LN7@funcion ; Si es 'c', branch
; Comparar con 'i' (0x69)
cmp BYTE PTR char[rsp], 105 ; 0x69 = 'i'
je SHORT $LN8@funcion ; Si es 'i', branch
Caso 'c' - Procesar String¶
$LN7@funcion:
; Incrementar puntero p
mov rax, QWORD PTR p$[rsp]
add rax, 8
mov QWORD PTR p$[rsp], rax
; Leer valor anterior (char*)
mov rax, QWORD PTR p$[rsp]
mov rax, QWORD PTR [rax-8] ; Dirección del string
mov QWORD PTR szarg$[rsp], rax
; Imprimir como string
mov rdx, QWORD PTR szarg$[rsp]
mov rcx, QWORD PTR std::cout
call std::operator<< ; cout << szarg
Caso 'i' - Procesar Entero¶
$LN8@funcion:
; Incrementar puntero p
mov rax, QWORD PTR p$[rsp]
add rax, 8
mov QWORD PTR p$[rsp], rax
; Leer valor anterior (int)
mov rax, QWORD PTR p$[rsp]
mov eax, DWORD PTR [rax-8] ; Solo 4 bytes (int)
mov DWORD PTR iarg$[rsp], eax
; Imprimir como entero
mov edx, DWORD PTR iarg$[rsp]
mov rcx, QWORD PTR std::cout
call std::basic_ostream::operator<< ; cout << iarg
Control del Loop¶
; Condición del for
mov eax, DWORD PTR i$[rsp]
inc eax
mov DWORD PTR i$[rsp], eax
; Comparar i con strlen(formato)
movsxd rax, DWORD PTR i$[rsp]
mov rcx, QWORD PTR formato$[rsp]
call strlen
cmp rcx, rax
jae $LN3@funcion ; Si i >= strlen, salir
Ventaja: No necesita verificar un centinela, usa el tamaño exacto del formato.
Argumentos del Main (argc, argv)¶
Declaración¶
int main(int argc, char* argv[]) {
// argc: número de argumentos (incluye el nombre del programa)
// argv: array de strings con los argumentos
return 0;
}
Componentes¶
argc (Argument Count):
- Tipo: int
- Contiene el número total de argumentos
- Incluye el nombre/path del programa como primer argumento
- Mínimo valor: 1 (solo el programa)
argv (Argument Vector):
- Tipo: char*[] (array de punteros a strings)
- argv[0]: Path completo del ejecutable
- argv[1] a argv[argc-1]: Argumentos adicionales
- argv[argc]: NULL (terminador)
Ejemplo¶
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
for (int i = 0; i < argc; i++) {
cout << argv[i] << endl;
}
return 0;
}
Ejecución: programa.exe Pepe Pipo loquito¶
Stack en x64:
Contenido de argv:
argv[0] → "C:\\path\\to\\programa.exe"
argv[1] → "Pepe"
argv[2] → "Pipo"
argv[3] → "loquito"
argv[4] → NULL
Output:
Análisis a Bajo Nivel¶
Convención x64¶
main PROC
mov DWORD PTR [rsp+8], ecx ; Guardar argc
mov QWORD PTR [rsp+16], rdx ; Guardar argv
sub rsp, 72
RCXcontieneargc(cantidad)RDXcontieneargv(dirección del array)
Estructura de argv en Memoria¶
argv → [Offset Ptr1] ─────→ "C:\\programa.exe\0"
[Offset Ptr2] ─────→ "Pepe\0"
[Offset Ptr3] ─────→ "Pipo\0"
[Offset Ptr4] ─────→ "loquito\0"
[NULL]
Cada elemento es un OFFSET (puntero de 8 bytes) a la string real.
Loop de Impresión¶
; Inicializar i = 0
mov DWORD PTR i$[rsp], 0
; Condición del loop
$LN2@main:
mov eax, DWORD PTR i$[rsp] ; Cargar i
cmp eax, DWORD PTR argc$[rsp] ; Comparar i con argc
jge SHORT $LN3@main ; Si i >= argc, salir
; Calcular argv[i]
movsxd rax, DWORD PTR i$[rsp] ; i a 64 bits
mov rcx, QWORD PTR argv$[rsp] ; Dirección de argv
mov rdx, QWORD PTR [rcx+rax*8] ; argv[i] (puntero al string)
; Imprimir
mov rcx, QWORD PTR std::cout
call std::operator<<
; i++
inc DWORD PTR i$[rsp]
jmp SHORT $LN2@main
$LN3@main:
xor eax, eax ; return 0
ret 0
Cálculo de dirección: argv[i] = *(argv + i * 8)
Configurar Argumentos en Visual Studio¶
Propiedades del Proyecto → Debugging → Command Arguments:
Configurar Argumentos en IDA Pro¶
Debugger → Process Options → Parameters:
Funciones Inline¶
Concepto¶
El modificador inline sugiere al compilador que inserte el código de la función directamente en el punto de llamada, en lugar de usar una instrucción call.
Declaración¶
inline int suma(int a, int b) {
return a + b;
}
int main() {
int x = suma(5, 3); // Puede expandirse inline
return 0;
}
Ventajas y Desventajas¶
| Ventajas | Desventajas |
|---|---|
✅ Más rápido: No hay overhead de call/ret |
❌ Mayor tamaño: Código duplicado |
| ✅ Sin stack frame: Ahorra push/pop | ❌ Cache: Más código puede afectar cache |
| ✅ Optimización: Mejor análisis del compilador | ❌ Recompilación: Cambios requieren recompilar todo |
Comportamiento del Compilador¶
Importante: inline es solo una sugerencia, no una orden.
El compilador decide si aplicar inline basándose en:
- Tamaño de la función (pequeñas son candidatas)
- Complejidad (loops, recursión → menos probable)
- Nivel de optimización (/O1, /O2, /Ox)
- Frecuencia de uso
Comparación: Normal vs Inline¶
Sin Inline¶
Ensamblador:
main:
mov edx, 3
mov ecx, 5
call suma ; Llamada a función
mov x, eax
suma:
add ecx, edx
mov eax, ecx
ret
Con Inline¶
Ensamblador (con optimización):
main:
mov eax, 8 ; Calculado directamente: 5 + 3
mov x, eax
; No hay call, código insertado directamente
Cuándo Usar Inline¶
✅ Buenas candidatas: - Funciones pequeñas (1-5 líneas) - Funciones llamadas frecuentemente - Getters/setters - Operaciones matemáticas simples
❌ Malas candidatas: - Funciones grandes (>10 líneas) - Funciones con loops - Funciones recursivas - Funciones con lógica compleja
Punteros a Funciones¶
Concepto¶
Un puntero a función almacena la dirección de memoria donde comienza el código de una función, permitiendo llamarla indirectamente.
Declaración¶
Ejemplos:
int (*pf1)(); // Puntero a función que devuelve int, sin parámetros
void (*pf2)(int); // Puntero a función void con parámetro int
float* (*pf3)(char*, int); // Puntero a función que devuelve float*, con char* e int
void (*pf4)(void (*)(int)); // Puntero a función que recibe otro puntero a función
int (*arr[10])(int); // Array de 10 punteros a funciones
Sintaxis Alternativa (usando typedef)¶
Ejemplo Básico¶
#include <iostream>
using namespace std;
int funcion() {
return 1;
}
int main() {
int (*pf1)(); // Declarar puntero a función
pf1 = funcion; // Asignar dirección de función
int x = pf1(); // Llamar función a través del puntero
cout << x << endl; // Output: 1
return 0;
}
Análisis a Bajo Nivel¶
Asignación de Dirección¶
lea rax, OFFSET FLAT:funcion ; Obtener dirección de función
mov QWORD PTR pf1[rsp], rax ; Guardar en puntero
pf1 ahora contiene: 0x00401050 (ejemplo de dirección)
Llamada Indirecta¶
vs llamada directa:
Ver Referencias en IDA¶
Para resolver calls indirectos en IDA:
- Ubicar el call indirecto:
call QWORD PTR pf1[rsp] - Edit → Plugins → Change Call Address
- Ingresar dirección:
0x401050 - Ahora
Ctrl+Xmostrará las referencias
Ejemplo 2: Selector de Funciones¶
#include <iostream>
using namespace std;
void muestra_1() { cout << "Muestra 1" << endl; }
void muestra_2() { cout << "Muestra 2" << endl; }
void muestra_3() { cout << "Muestra 3" << endl; }
void muestra_4() { cout << "Muestra 4" << endl; }
int main() {
void (*pf1)(); // Puntero a función
int num;
do {
cout << "Introduce un número (1-4, 0 para salir): ";
cin >> num;
if (num >= 1 && num <= 4) {
switch (num) {
case 1: pf1 = muestra_1; break;
case 2: pf1 = muestra_2; break;
case 3: pf1 = muestra_3; break;
case 4: pf1 = muestra_4; break;
}
pf1(); // Llamar función seleccionada
}
} while (num != 0);
return 0;
}
Flujo¶
Ensamblador del Switch¶
; Comparar con 1
cmp DWORD PTR num[rsp], 1
jne SHORT $L2
; Case 1: pf1 = muestra_1
lea rax, OFFSET FLAT:muestra_1
mov QWORD PTR pf1[rsp], rax
jmp SHORT $L_exit
$L2:
; Case 2: pf1 = muestra_2
cmp DWORD PTR num[rsp], 2
jne SHORT $L3
lea rax, OFFSET FLAT:muestra_2
mov QWORD PTR pf1[rsp], rax
; ... y así con 3 y 4
$L_exit:
; Llamar función seleccionada
call QWORD PTR pf1[rsp]
Ejemplo 3: Array de Punteros a Funciones¶
#include <iostream>
using namespace std;
void f1(int n) { while (n--) cout << "Muestra 1 "; cout << endl; }
void f2(int n) { while (n--) cout << "Muestra 2 "; cout << endl; }
void f3(int n) { while (n--) cout << "Muestra 3 "; cout << endl; }
void f4(int n) { while (n--) cout << "Muestra 4 "; cout << endl; }
int main() {
void (*pf1[4])(int); // Array de 4 punteros a funciones
// Inicializar array
pf1[0] = f1;
pf1[1] = f2;
pf1[2] = f3;
pf1[3] = f4;
int num, valores;
do {
cout << "Introduce función (1-4, 0 para salir): ";
cin >> num;
if (num >= 1 && num <= 4) {
cout << "Introduce cantidad (1-10): ";
cin >> valores;
if (valores > 0 && valores < 11) {
pf1[num - 1](valores); // Llamar con índice ajustado
}
}
} while (num != 0);
return 0;
}
Estructura en Memoria¶
pf1 → [Offset f1] → Código de f1
[Offset f2] → Código de f2
[Offset f3] → Código de f3
[Offset f4] → Código de f4
Cada elemento ocupa 8 bytes (QWORD en x64).
Inicialización en Ensamblador¶
; pf1[0] = f1
lea rcx, OFFSET FLAT:f1
mov QWORD PTR pf1[rsp+0], rcx
; pf1[1] = f2
lea rcx, OFFSET FLAT:f2
mov QWORD PTR pf1[rsp+8], rcx
; pf1[2] = f3
lea rcx, OFFSET FLAT:f3
mov QWORD PTR pf1[rsp+16], rcx
; pf1[3] = f4
lea rcx, OFFSET FLAT:f4
mov QWORD PTR pf1[rsp+24], rcx
Offsets: 0, 8, 16, 24 (incrementos de 8 bytes)
Llamada con Índice¶
; Calcular pf1[num - 1]
mov eax, DWORD PTR num[rsp]
dec eax ; num - 1
movsxd rax, eax ; Extender a 64 bits
imul rax, 8 ; Multiplicar por 8 (tamaño de puntero)
; Obtener dirección de función
lea rcx, QWORD PTR pf1[rsp]
add rcx, rax ; pf1 + (num-1)*8
mov rax, QWORD PTR [rcx] ; Cargar puntero a función
; Pasar argumento y llamar
mov ecx, DWORD PTR valores[rsp]
call rax ; Call indirecto
Cálculo: pf1[num-1] = *(pf1 + (num-1) * 8)
Utilidad de Punteros a Funciones¶
- Callbacks: Pasar funciones como parámetros
- Polimorfismo en C: Simular comportamiento orientado a objetos
- Tablas de dispatch: Jump tables para optimización
- Plugins/Extensiones: Cargar funcionalidad dinámica
- State machines: Cambiar comportamiento según estado
Ejemplo: Función que Recibe Puntero a Función¶
void ejecutar(int (*func)(int), int valor) {
int resultado = func(valor);
cout << "Resultado: " << resultado << endl;
}
int doble(int x) { return x * 2; }
int triple(int x) { return x * 3; }
int main() {
ejecutar(doble, 5); // Output: Resultado: 10
ejecutar(triple, 5); // Output: Resultado: 15
return 0;
}
Resumen de Técnicas Avanzadas¶
Comparación de Métodos para Argumentos Variables¶
| Método | Ventajas | Desventajas |
|---|---|---|
| Centinela (0) | Simple, automático | Requiere valor especial, no puede usar 0 como dato |
| Tipo en arg fijo | Distingue categorías | No sabe cantidad exacta |
| String de formato | Preciso, autodescriptivo | Requiere formato correcto |
| Count explícito | Más seguro, flexible | Requiere contar argumentos |
Convenciones x64 - Resumen¶
Registros para argumentos:
1º → RCX
2º → RDX
3º → R8
4º → R9
5+ → Stack ([RSP+32], [RSP+40], ...)
Shadow Space: [RSP+8] a [RSP+32] (siempre reservado)
Patterns Comunes en Reversing¶
Puntero a función:
Array de punteros a funciones:
Variadic con formato: