【问题标题】:c++ lambda, cannot access variable captured by copyc ++ lambda,无法访问复制捕获的变量
【发布时间】:2014-03-01 02:26:35
【问题描述】:

我有这个发送和接收 lambda 函数的简单客户端/服务器套接字代码。问题出在 recvlambda() 函数内部的客户端上,当我在收到它后尝试调用 lambda 时,我得到 seg fault @

printf("Hello World! %i, %i\n", x, y);

调试发现 x 和 y 无法访问,它们的内存地址是错误的。

我在 Ubuntu 13.10 上使用 gcc 4.8.1。

我通过将 x、y 复制到 sendlambda() 函数中的 lambda 来传递。这不应该是段错误。知道为什么吗?

#include <iostream>
#include <time.h>
#include <gmpxx.h>

using namespace std;

typedef int (*func)();

/* Server code in C */

  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
#include <functional>

void sendlambda(int ConnectFD)
{
    int x = 2342342;
    int y = 23454234;
    function<int (void)> f = [x, y]() mutable -> int
    {
       printf("Hello World! %i, %i\n", x, y);
    };
    printf("sending lambda of %i bytes\n", sizeof(f));
    write(ConnectFD, (void*)&f, sizeof(f));
}


void recvlambda(int SocketFD)
{
    char buffer[1024];
    read(SocketFD, (void*)buffer, sizeof(buffer));
    function<int (void)> &f = *(function<int (void)>  *)buffer;
    f();
}

int server()
{
    printf("server\n");
    struct sockaddr_in stSockAddr;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    if(-1 == SocketFD)
    {
        printf("can not create socket\n");
        exit(EXIT_FAILURE);
    }

    memset(&stSockAddr, 0, sizeof(stSockAddr));

    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    if(-1 == bind(SocketFD,(struct sockaddr *)&stSockAddr, sizeof(stSockAddr)))
    {
        printf("error bind failed\n");
        close(SocketFD);
        exit(EXIT_FAILURE);
    }

    if(-1 == listen(SocketFD, 10))
    {
        printf("error listen failed\n");
        close(SocketFD);
        exit(EXIT_FAILURE);
    }

    for(;;)
    {
        int ConnectFD = accept(SocketFD, NULL, NULL);

        if(0 > ConnectFD)
        {
            printf("error accept failed\n");
            close(SocketFD);
            exit(EXIT_FAILURE);
        }

        /* perform read write operations ...*/
        sendlambda(ConnectFD);

        if (-1 == shutdown(ConnectFD, SHUT_RDWR))
        {
            printf("can not shutdown socket\n");
            close(ConnectFD);
            close(SocketFD);
            exit(EXIT_FAILURE);
        }
        close(ConnectFD);
    }

    close(SocketFD);
    return EXIT_SUCCESS;
}


int client()
{
    printf("client\n");
    struct sockaddr_in stSockAddr;
    int Res;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

    if (-1 == SocketFD)
    {
        printf("cannot create socket\n");
        exit(EXIT_FAILURE);
    }

    memset(&stSockAddr, 0, sizeof(stSockAddr));

    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    Res = inet_pton(AF_INET, "127.0.0.1", &stSockAddr.sin_addr);

    if (0 > Res)
    {
        printf("error: first parameter is not a valid address family\n");
        close(SocketFD);
        exit(EXIT_FAILURE);
    }
    else if (0 == Res)
    {
        printf("char string (second parameter does not contain valid ipaddress\n)");
        close(SocketFD);
        exit(EXIT_FAILURE);
    }

    if (-1 == connect(SocketFD, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr)))
    {
        printf("connect failed\n");
        close(SocketFD);
        exit(EXIT_FAILURE);
    }

    /* perform read write operations ... */
    recvlambda(SocketFD);

    (void) shutdown(SocketFD, SHUT_RDWR);

    close(SocketFD);
    return EXIT_SUCCESS;
}

int main(int argc, char** argv)
{
    if(argc>1 && strcmp(argv[1], "server")==0)
        server();
    else
        client();
    return 0;
}

【问题讨论】:

  • 你不能像这样简单地通过套接字传递 readwrite 对象,除了可简单复制的类型,比如 C 中的那些。你知道如果你以正确的方式发送 char* 会发生什么? (不是它指向的,指针本身)。同样的事情。
  • 1.您可以(并且应该)使用auto 来声明f 2. 无需使其可变,您永远不会修改内部状态 3. 您说它返回int 但事实并非如此。当然,4. 你不能返回一个局部变量的地址(这就是一个 lambda,真的)
  • 还要注意这里的问题不是lambda本身,而是function&lt;int(void)&gt;保持状态。
  • @MooingDuck 可变和返回 int 是我的错误,我应该删除它们。尽管如此,这对我的问题没有任何影响。此外,您对 char* 的评论在这里无效,因为我没有复制任何指针。
  • @rosewater:不正确,您正在复制指针。现在想象一下:struct player {const char* name;} 显然读/写player 也是无效的,对吗?好吧,function&lt;int(void)&gt; 内部同样包含一个指针。

标签: c++ lambda


【解决方案1】:

一般来说,您不能指望按位复制std::function 等复杂类型会正常工作。

以下是可能发生的情况:在服务器进程中创建的 std::function 对象持有一个函数指针,指向您用来初始化它的 lambda,它驻留在服务器进程的内存中。当客户端进程接收到它时,它会尝试调用这个函数指针,该指针指向不同进程的地址空间(服务器进程的地址空间)。这个段错误并不奇怪。

但无论如何你都处于未定义的行为领域,因为你分配了一个字符数组,然后试图访问它,就好像它是一个 std::function 对象一样。

【讨论】:

  • 如果我不使用 x 和 y,一切正常。我认为当您将变量复制到 lambda 时,它会制作它们的“副本”,而不是使用引用。并且客户端进程尝试在自己的地址空间中调用 lambda,而不是在服务器中。
  • 它可能在您的系统上运行良好,但这并不意味着您应该这样做。一般来说,它可能会或可能不会起作用,就像所有其他“未定义的行为”一样。
  • 来自此规范:en.cppreference.com/w/cpp/language/lambda 如果 lambda 表达式通过复制捕获任何内容(使用捕获子句 [=] 隐式捕获或使用不包含字符 & 的捕获显式捕获,例如 [a, b, c]),闭包类型包括未命名的非静态数据成员,以未指定的顺序声明,保存所有被捕获的实体的副本。
  • 嗯,但事实仍然是,将std::function 对象的对象表示复制到字符数组中不会产生另一个std::function 对象,在标准的观点。
  • 我希望按字节复制可以工作并产生另一个 std::function 对象。如果它不是闭包,它似乎确实有效。
【解决方案2】:

我又尝试了一些东西,并得出结论,我正在尝试做的事情类似于序列化 lambda。 Serialize C++ functor

这是不可能的。

当客户端代码在某个地方(在它自己的进程空间中)使用或定义函子,然后接收到一些被类型转换为同一函子的字节流时,代码就可以工作了。它以某种方式在客户端调用该函数。

但是,如果客户端进程没有仿函数块,并且您尝试将字节流类型转换为仿函数,则它不起作用,您无法调用该仿函数。

如果它是一个闭包,那就更糟了,服务器上复制捕获的变量无法映射到客户端。服务器进程的数据段中的变量,它们在服务器的函子中被引用。当这个函子被发送到客户端时,客户端会尝试访问给定数据段内存地址的那些变量。但是这些地址是用于服务器进程的。所以我遇到了读取访问冲突。

我好像在尝试使用 lambda 进行代码注入,但这是不可能的。

【讨论】:

    【解决方案3】:

    lambda 只是另一个本地分配在sendlambda 的麻袋上。 &amp;f 函数与获取该函数中声明的任何其他本地的地址没有什么不同。在这种情况下,尽管f 不是一种可以简单地按值复制并在另一个帧中重新水合的类型(它不是 POD 类型)。这就是您在recvlambda 中遇到错误的原因

    【讨论】:

    • 客户端在另一个进程中没有得到栈上一个本地的地址。它实际上是获取函子代码,并将地址映射到客户端进程的地址空间。
    • @rosewater 它仍然指的是堆栈分配的 lambda 类
    • @JaredPar:我认为他没有发送任何堆栈分配对象的地址。他传递了一个 std::function&lt;int(void)&gt; ,就好像它可以简单地复制一样,它包含 lambda 的私有副本,无论是在其内部还是在堆上。
    • @JaredPar:MooingDuck 是对的。将局部变量的地址传递给 write() 是正确的,如果局部变量是 POD 类型 -- write() 只是复制变量的字节,这对于 POD 类型来说已经足够了。这里的问题是,std::function&lt;int(void)&gt; 类型的 f 不能仅通过复制字节来复制。
    猜你喜欢
    • 1970-01-01
    • 2019-03-25
    • 2018-02-06
    • 2011-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多