new 在 lambda

看到一个问题,如何把 lambda 弄到 new 出来的地址上。

一开始的想法是:lambda 在声明的时候就已经是 rv 了,而 trivial move ctor 是 优先 move 的 bitwise copy,那肯定搞不定啊。

举例说:

something = move([]{}); // 此时,某个匿名函数已经构造完毕,所以 这里的实际代码是:

something.m_a = move(__unnamed.m_a);
something.m_b = move(__unnamed.m_b);
...

等于白搭。

但两次询问问题人后其确定 void* p = (void*) new sometype([]{}); 的语法是能够把 lambda 的构造弄到 new 出来的内存上的。于是开干吧。

首先查阅 gcc 源码,得知其 lambda 命名规则是 __lambda%d,%d 跟第 lambdacnt 个。所以 第 1 个 lambda 的类的名字是 __lambda0

int main() {
    class __lambda0;
    void* p = (void*) new __lambda0([]{});
    (*(__lambda0*)p)();
}

这样。

然后查阅汇编代码,实际上因为我对汇编不是很熟,对比了两份。

——— 1 ————

#include <iostream>

int main() {
    int a = 10, b = 20, c = 30, d = 40, e = 50;
    class __lambda0;
    auto f = [a, b, c, d, e]{static int a; static int c; int b = 1025;};
    f();
}

———- 2 ————-

#include <iostream>

int main() {
    int a = 10, b = 20, c = 30, d = 40, e = 50;
    class __lambda0;
    void* p = (void*) new __lambda0([a, b, c, d, e]{static int a; static int c; int b = 1025;});
    (*(__lambda0*)p)();
}

对照着看:

———- 1 ————-

_main:
LFB1139:
.cfi_startproc
pushl   %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl    %esp, %ebp
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $48, %esp
call    ___main
movl    $10, 44(%esp)
movl    $20, 40(%esp)
movl    $30, 36(%esp)
movl    $40, 32(%esp)
movl    $50, 28(%esp)
movl    44(%esp), %eax
movl    %eax, 8(%esp)
movl    40(%esp), %eax
movl    %eax, 12(%esp)
movl    36(%esp), %eax
movl    %eax, 16(%esp)
movl    32(%esp), %eax
movl    %eax, 20(%esp)
movl    28(%esp), %eax
movl    %eax, 24(%esp)
leal    8(%esp), %eax
movl    %eax, %ecx
call    __ZZ4mainENKUlvE_clEv # call lambda func

———- 2 ————-

_main:
LFB1139:
.cfi_startproc
pushl   %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl    %esp, %ebp
.cfi_def_cfa_register 5
andl    $-16, %esp
subl    $48, %esp
call    ___main
movl    $10, 44(%esp)
movl    $20, 40(%esp)
movl    $30, 36(%esp)
movl    $40, 32(%esp)
movl    $50, 28(%esp)
movl    $20, (%esp)
call    __Znwj # new
movl    44(%esp), %edx
movl    %edx, (%eax)
movl    40(%esp), %edx
movl    %edx, 4(%eax)
movl    36(%esp), %edx
movl    %edx, 8(%eax)
movl    32(%esp), %edx
movl    %edx, 12(%eax)
movl    28(%esp), %edx
movl    %edx, 16(%eax)
movl    %eax, 24(%esp)
movl    24(%esp), %eax
movl    %eax, %ecx
call    __ZZ4mainENKUlvE_clEv # call lambda func

可以看到

非 new 的版本里:

movl    $10, 44(%esp) # *(44+esp) = 10 // a=10
...
# 开始初始化
movl    44(%esp), %eax # eax = *(44+esp) // eax = a;
movl    %eax, 8(%esp) # *(8+esp) = eax; # 这里初始化 __lambda0 // __lambda0.a = eax;

new 的版本里:

movl    $10, 44(%esp) # *(44+esp) = 10; // a = 10;

call    __Znwj # new(20) 这个 20 装在 *(esp) 里 // new(20);
movl    44(%esp), %edx # edx = *(44+esp) // edx = a;
movl    %edx, (%eax) # *eax = edx; // 这里的 eax 就是 new 出来的内存的起始地址(是规定)// __lambda0.a = edx

我在这里的判断中犯的错误是,把 lambda 当成如同 std 里面的类型了。但 lambda 不是的。它是一种内置类型,可以享受内置类型带来的各种优越条件。同样的例子可以看:

int* a = new int(10); 

谁都不可能觉得这个 10 是先弄出一个临时量再被 copy 到 *a 中的。

补充两个:

  1. 至少 gcc 的实现里,栈是以 16byte 为单位增长的。
  2. new(size) 的 size 是 long long,放在 0(esp);

Leave a Reply

Your email address will not be published. Required fields are marked *