11 - Functors y lambda functions ...
¿Qué es un functor?
Un functor es una clase que tiene definido el operator() como member function. Por ejemplo, observa la clase amAFunctor.
class amAFunctor {
public:
void operator()() {
printf("Hello functor\n");
};
};
Al tener definido el operator(), dado un objeto f de la clase amAFunctor, podemos aplicarle el operator ().
int main()
{
amAFunctor f; // declaración de objeto f
f(); // Imprime "hello functor"
return 0;
}
He aquí el resultado de compilar ese programa. Nota que el compilador creo la member function operator()() para la clase amAFunctor.
"amAFunctor::operator()()":
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov edi, OFFSET FLAT:.LC0
call "puts"
nop
leave
ret
"main":
push rbp
mov rbp, rsp
sub rsp, 16
lea rax, [rbp-1]
mov rdi, rax
call "amAFunctor::operator()()"
mov eax, 0
leave
ret
¿Cómo se relacionan las funciones lambda a los functors?
Las funciones lambda (tambien conocidas como funciones anónimas) en C++ son convertidas por el compilador en functors! Mira en lo que se convierte este programa:
int main()
{
auto amALambda = []() {
printf("Hello lambda\n");
};
amALambda();
return 0;
}
Exactamente en lo que se había convertido con el functor! (con excepción de que la clase usa otro nombre, el compilador le llamó lambda a esa clase).
"main::'lambda'()::operator()() const":
push rbp
mov rbp, rsp
sub rsp, 16
mov QWORD PTR [rbp-8], rdi
mov edi, OFFSET FLAT:.LC0
call "puts"
nop
leave
ret
"main":
push rbp
mov rbp, rsp
sub rsp, 16
lea rax, [rbp-1]
mov rdi, rax
call "main::'lambda'()::operator()() const"
mov eax, 0
leave
ret
Las funciones lambda tienen la capacidad de capturar variables locales de la función donde son invocadas. Por ejemplo:
int main()
{
int n = 8;
auto amALambdaWCapture = [&]() {
printf("Hello lambda %d\n", n);
};
amALambdaWCapture(); // Imprime "Hello lambda 8"
n = 42;
amALambdaWCapture(); // Imprime "Hello lambda 42"
return 0;
}
Es compilado a:
01 "main::'lambda'()::operator()() const":
02 push rbp
03 mov rbp, rsp
04 sub rsp, 16
05 mov QWORD PTR [rbp-8], rdi
06 mov rax, QWORD PTR [rbp-8]
07 mov rax, QWORD PTR [rax]
08 mov eax, DWORD PTR [rax]
09 mov esi, eax
10 mov edi, OFFSET FLAT:.LC0
11 mov eax, 0
12 call "printf"
13 nop
14 leave
15 ret
16 "main":
17 push rbp
18 mov rbp, rsp
19 sub rsp, 16
20 mov DWORD PTR [rbp-4], 8. ; n = 8
21 lea rax, [rbp-4] ; rax = &n
22 mov QWORD PTR [rbp-16], rax. ; [rbp-16] = &n
23 lea rax, [rbp-16] ; rax = rbp-16
24 mov rdi, rax ; primer arg = dirección de obj
25 call "main::'lambda'()::operator()() const"
26 mov DWORD PTR [rbp-4], 42
27 lea rax, [rbp-16]
28 mov rdi, rax
29 call "main::'lambda'()::operator()() const"
30 mov eax, 0
31 leave
32 ret
Observaciones:
-
En las líneas 20-24: se copia un 8 al objeto que vive en [rbp-16] y luego se pasa esa dirección a la función
'lambda'()::operator()() const. -
La función
'lambda'()::operator()() constrecibe la dirección del objeto (a través de rdi, línea 05). Desreferencia esa dirección (línea 07) para obtener acceso al objeto. Luego desreferencia el primer valor dentro del objeto (línea 8), que corresponde a un puntero al valor de n.
Qué significa para reverse engineering
El efecto de usar lambda functions lo experimentamos cuando analizamos un programa de Qt que las usaba:
1 #include <QApplication>
2 #include <QMainWindow>
3 #include <QMenuBar>
4 #include <QAction>
5 #include <QMessageBox>
6
7 int main(int argc, char *argv[])
8 {
9 QApplication app(argc, argv);
10 QMainWindow w;
11
12 int cnt = 0;
13 QMenu *file = w.menuBar()->addMenu(QObject::tr("&File"));
14 QAction *hello = file->addAction(QObject::tr("&Say Hello"));
15 QAction *info = file->addAction(QObject::tr("&Info"));
16 QAction *quit = file->addAction(QObject::tr("&Quit"));
17
18 // CONECTAR A SAY HELLO
19 QObject::connect(hello, &QAction::triggered, [&]{
20 QString message = "Not yet!";
21 if (cnt > 2) message = "Hello, World!";
22 QMessageBox::information(&w, message, message);
23 });
24
25 // CONECTAR A INFO
26 QObject::connect(info, &QAction::triggered, [&]{
27 cnt++;
28 QMessageBox::information(&w, "Info", QString::number(cnt));
29 });
30
31 QObject::connect(quit, &QAction::triggered, &app, &QApplication::quit);
32
33 w.resize(400, 200);
34 w.show();
35 return app.exec();
36 }
La función principal del programa creaba las opciones del menú y conectaba una función con cada dos de las opciones (Say Hello e Info) (lineas 18-29).
Nota que en ambas conecciones se utiliza una función lambda. Por ejemplo la del primer connect es:
[&]{
QString message = "Not yet!";
if (cnt > 2) message = "Hello, World!";
QMessageBox::information(&w, message, message);
}
.. en otras palabras, una función lambda que NO recibe argumentos (no tiene “( )”). El “[&]” es n lambda capture clause y significa que esa función tendrá acceso a las variables locales de la función en la que se encuentra (en este caso main)
Esta es la descompilación de esa función lambda:
1 void __thiscall
2 QtPrivate::detail::StorageByValue<>::anon_struct_16_2_0337a457_for_o::operator()
3 (anon_struct_16_2_0337a457_for_o *this)
4
5 {
6 undefined8 *in_RCX;
7 QString *pQVar1;
8 QFlags<> *in_stack_ffffffffffffff98;
9 StandardButton in_stack_ffffffffffffffa0;
10 QString message;
11 uint local_1c;
12
13 /* Unresolved local var: QMainWindow * w@[???] */
14 QString::QString(&message,"Not yet!");
15 if (2 < *(int *)*in_RCX) {
16 QString::operator=(&message,"Hello, World!");
17 }
18 QFlags<>::QFlags(in_stack_ffffffffffffff98,in_stack_ffffffffffffffa0);
19 pQVar1 = &message;
20 QMessageBox::information
21 ((QWidget *)in_RCX[1],(QString *)pQVar1,(QString *)&message,
22 (QFlags *)(ulonglong)local_1c,NoButton);
23 QString::~QString(&message,(int)pQVar1);
24 return;
25 }turn;
}
Observaciones:
- El compilador le llama
operator()a la función, consistente con el hecho de que en C++ las funciones lambda se convierten en functors. - La función recibe un argumento, que el compilador denomina de tipo
anon_struct_16_2_0337a457_for_o. - Dado que el functor que representa esa función lambda necesita acceso a variables locales como
cnt, el valor de esa variable tiene que ser pasado a ese función. Aparentemente, se pasa usando un puntero a la estructura tipoanon_struct_16_2_0337a457_for_o. Usando la funcionalidad “Edit Data Type” sobreanon_struct_16_2_0337a457_for_ovemos que Ghidra identificó que esa estructura contiene un puntero a entero ( a la variablecnt)
