V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
aigebiu
V2EX  ›  C

vector<T> 和 T[] 内存地址区别

  •  
  •   aigebiu · 2016-05-05 16:19:14 +08:00 · 3244 次点击
    这是一个创建于 2907 天前的主题,其中的信息可能已经有所发展或是发生改变。

    用 libnetfilter_queue 获得数据包, libtins 操作数据包,放回 netfilter 时出现问题:

    vector<uint8_t> vec = pkt.serialize();
    nfq_set_verdict(qh, id, NF_ACCEPT, size, &vec[0]);
    

    上面这样,(据我测试)只有长度 135 字节以上的数据包才能成功放回,而这样:

    uint8_t arr[MAX_BUFSIZ];
    copy(vec.begin(), vec.end(), arr);
    nfq_set_verdict(qh, id, NF_ACCEPT, size, arr);
    

    就完全没问题。

    为什么呢?&vec[0] 和 arr 的地址有什么区别么?

    按理说 vector 也是一段连续内存,应该一样的啊。求解,多谢!

    第 1 条附言  ·  2016-05-05 18:28:25 +08:00
    size = vec.size();

    摘一些代码实现,也许有用

    ```c++
    PDU::serialization_type PDU::serialize() {
    vector<uint8_t> buffer(size());
    serialize(&buffer[0], static_cast<uint32_t>(buffer.size()), 0);
    return buffer;
    }
    ```

    ```c++
    static int __set_verdict(...const unsigned char *data...)
    {
    ...
    /* The typecast here is to cast away data's const-ness: */
    nfnl_build_nfa_iovec(&iov[1], &data_attr, NFQA_PAYLOAD,
    data_len, (unsigned char *) data);
    ...
    return nfnl_sendiov(qh->h->nfnlh, iov, nvecs, 0);
    }
    ```

    基本就是调 nfnetlink ,先包装成 iovec (话说外面把 vector 拆成 size 和*data ,里面又组合起来累不累啊……直接传 iovec 多好 233 ),然后 nfnl_sendiov 发给内核。不过会不会和 const 有关呢?
    第 2 条附言  ·  2016-05-05 19:03:21 +08:00
    去掉 const 重新编译了下 nfqueue 库,一样,还是 135 字节以上就行。应该和 nfqueue 库无关,是 vector 的问题。
    kernel 4.4.0 ubuntu 14.04.01 x64
    第 3 条附言  ·  2016-05-05 23:50:30 +08:00
    编译到树莓派 2 ( openwrt 15.05.1 )上试了下,有同样问题,不过成功/失败的临界大小变成 69 字节, 32 位 ub14 则是 61 字节;而 vector 内存 copy 一遍后,都能正常运行。

    上述场景中,变量主要是标准库和架构,其他库都是从新编译的,看来 STL 小块内存的解释最靠谱。

    openwrt 的 libc/c++: libuClibc-0.9.33.2.so libstdc++.so.6.0.19
    第 4 条附言  ·  2016-05-06 12:57:13 +08:00
    template <typename T>
    class alloc {
    public:
        typedef T           value_type;
        typedef T*          pointer;
        typedef std::size_t size_type;
    
        alloc() throw() {}
        ~alloc() throw() {}
    
        value_type pool[1<<20];
        size_type cur = 0;
    
        pointer allocate(size_type num, const void* = 0)
        {
            pointer p = &pool[cur];
            cur += num;
            return p;
        }
        void deallocate(pointer p, size_type num) {}
    };
    
    vector< uint8_t, alloc<uint8_t> > vec(size);
    

    用这个allocator开的vector,没有上述问题。用malloc或者默认的new开的vector则存在问题。

    第 5 条附言  ·  2016-05-06 12:59:49 +08:00
    但数组不管是静态开的,还是 malloc/new 的,都没问题。
    第 6 条附言  ·  2016-05-06 13:20:27 +08:00

    我有点明白了…… 如下,删掉free/delete,用malloc/new就也没问题了

        pointer allocate(size_type num, const void* = 0)
        {
            return (pointer)malloc(num * sizeof(value_type));
        }
        void deallocate(pointer p, size_type num) {}
    

    不让vector回收掉内存即可。

    但回收内存时函数已返回,netlink应当已经送出iovec消息,将vector的这段内存拷贝进内核了,外面回收内存不会影响它了啊……

    第 7 条附言  ·  2016-05-06 13:44:12 +08:00

    额……大家散了吧……

    原因是vector在scope结束时deallocate内存,然后我的vector定义在一个if里,还没等调用netlink就回收了。 去掉if或定义在外面就好了。

    不过还是有一些收获。比如知道了malloc/new会重复利用小块内存:

    alloc 29 bytes at 28261216
    alloc 29 bytes at 28261216 #复用小块内存,netlink re-inject失败
    alloc 233 bytes at 28261456 # re-inject成功
    alloc 29 bytes at 28261216
    alloc 233 bytes at 282614328 # 新开内存,re-inject成功
    

    所以小包未能成功放回(回收后的内存很快挪作他用了),大包则没问题(每次都是新开的内存)。

    @MCVector 说的对,和vector没关系。(*/ω\*) 掩面而逃233

    第 8 条附言  ·  2016-05-06 14:02:40 +08:00
    更正:不一定每次新开大块内存,取决于具体情况。

    glibc malloc 介绍 https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/
    38 条回复    2016-05-07 09:46:41 +08:00
    mug
        1
    mug  
       2016-05-05 17:23:15 +08:00
    vector 的内部不一定是连续的。
    Monad
        2
    Monad  
       2016-05-05 17:38:28 +08:00   ❤️ 1
    反对楼上 摘自 N3690.pdf
    没用过 libnetfilter_queue, 楼主你的问题我猜测是 size 有问题...
    araraloren
        3
    araraloren  
       2016-05-05 17:43:56 +08:00
    不要这么用 vector 以及其它模板, C 接口就老老实实使用 C 数据结构。。
    kkhaike
        4
    kkhaike  
       2016-05-05 17:58:22 +08:00
    我也觉得是 size 有问题,你把他替换成 vec.size()试试
    aigebiu
        5
    aigebiu  
    OP
       2016-05-05 18:18:47 +08:00
    @Monad @kkhaike size=vec.size(); 抱歉忘说了

    我之所以问内存区别,是推测问题可能跟 vector 分配内存的方式有关(倍增 capacity )
    但 new 一个数组也没问题,和静态开的一样 就想不明白了……
    bossfrog
        6
    bossfrog  
       2016-05-05 18:49:22 +08:00 via Android   ❤️ 1
    @mug 标准保证连续
    boydfd
        7
    boydfd  
       2016-05-05 19:05:00 +08:00
    stl 里面小块内存(比如 128 以下)是通过内存池分配的。大于 128 才会通过全局的 malloc 分配。问题应该就出在这,你结合自己的程序考虑一下看
    aigebiu
        8
    aigebiu  
    OP
       2016-05-05 19:53:24 +08:00
    @boydfd 谢谢!应该是这个原因 vec.reserve() 一下就好了…… 能否具体讲下小块内存的“内存池”?或者相关资料?多谢
    Monad
        9
    Monad  
       2016-05-05 20:04:41 +08:00
    @aigebiu 这种东西对上层来说应该是透明的,不应该是这个影响,除非你依赖了某些分割(比如\0 )之类的,
    另外把 const unsigned char* 转成 unsigned char* ,如果传入的参数本身就是 const ,那这是未定义行为,也有可能会影响结果。
    如果可以上段完整的 gist ,我编译调一调。
    boydfd
        10
    boydfd  
       2016-05-05 20:13:08 +08:00
    @aigebiu stl 源码剖析这本书上有介绍
    allenx
        11
    allenx  
       2016-05-05 20:26:54 +08:00
    vector 内部连续,可以当做起始地址为:&vec[0],长度为 sizeof(T)*vec.size()的一段内存。
    aigebiu
        12
    aigebiu  
    OP
       2016-05-05 21:06:34 +08:00
    @Monad 截出一个片段 https://gist.github.com/isofew/3aa6c0eb716655fa60e53e46ae298d56 我觉得还是 stl 实现的问题
    neoblackcap
        13
    neoblackcap  
       2016-05-05 21:08:30 +08:00
    c++03 开始已经要求 vector 对内部存储要连续了。
    neoblackcap
        14
    neoblackcap  
       2016-05-05 21:08:46 +08:00
    @mug c++03 开始已经要求 vector 对内部存储要连续了。
    kkhaike
        15
    kkhaike  
       2016-05-05 21:27:22 +08:00
    看了下源码,我觉得原因应该是这个传入的长度一定要是 4 的倍数
    ```
    #define NFA_ALIGNTO 4
    #define NFA_ALIGN(len) (((len) + NFA_ALIGNTO - 1) & ~(NFA_ALIGNTO - 1))
    ```
    再加上 @boydfd 所说的 ,小内存是内存池,越界访问出了问题?“成功放回”指的是什么?
    反正 vector 一定是内部连续的,这样使用确实没问题。
    MCVector
        16
    MCVector  
       2016-05-05 21:28:00 +08:00 via Android
    std::vector 表示这个锅我不背
    aigebiu
        17
    aigebiu  
    OP
       2016-05-05 22:03:01 +08:00
    @kkhaike 成功将修改后的包放回 netfilter 然后走 routing->forward->postrouting->网卡 出去
    现在只是 verdict 传回去了,包的内容没传过去,还是原来的包,走 routing->input->用户进程 这条路了。
    4 字节的包一样不行。
    ryd994
        18
    ryd994  
       2016-05-05 22:28:38 +08:00
    uint8_t * arr =malloc(size*8);
    copy(vec.begin(), vec.end(), arr);
    nfq_set_verdict(qh, id, NF_ACCEPT, size, arr);

    这样会怎样?
    aigebiu
        19
    aigebiu  
    OP
       2016-05-05 22:46:25 +08:00
    @ryd994 成功放回
    colatin
        20
    colatin  
       2016-05-06 00:09:10 +08:00
    依稀记得 1byte vector 有优化,内部并不是 uint8_t []方式分配内存的
    colatin
        21
    colatin  
       2016-05-06 00:09:44 +08:00
    @colatin 记错了,是 bool 类型的
    colatin
        22
    colatin  
       2016-05-06 00:15:45 +08:00
    应该是内存池和对齐的问题
    proudzhu
        23
    proudzhu  
       2016-05-06 09:03:51 +08:00 via Android
    人家 vector 就不是给你这么用的,出问题了去看标准库实现,猜来猜去没啥用
    zwzmzd
        24
    zwzmzd  
       2016-05-06 09:21:38 +08:00
    试一下如下的代码呢?

    ···
    vector<uint8_t> v_arr(MAX_BUFSIZ);
    copy(vec.begin(), vec.end(), v_arr.begin());
    nfq_set_verdict(qh, id, NF_ACCEPT, size, &v_arr[0]);
    ···

    我看了一下你提到的两种实现,第一种使用 vector 的实现,主要数据是放在堆中的;而第二种开辟临时数组的方式,数据是存放在栈上的。相对来讲,数据放在堆上很有可能因为编程者的失误导致部分区域被覆盖,建议检查下之前有没有内存越界的错误。

    C++里面我一直是使用 vector+resize()代替 malloc()的,这样有个好处是块临时变量会在块结束时自动释放。用到现在还没发现&vec[0]和 arr[]有任何区别
    linux40
        25
    linux40  
       2016-05-06 10:34:08 +08:00 via Android
    @zwzmzd &vec[0]和 arr[]没区别是建立在你用的 Alloc 是 std::allocator ,标准没有说 Alloc::pointer 一定是指针(T*)哟,标准也没有说&t 的返回值一定是指针哟。
    zwzmzd
        26
    zwzmzd  
       2016-05-06 11:01:50 +08:00
    @linux40 是指 operator& 重载么? LZ 遇到的应该不是这个问题吧
    aigebiu
        27
    aigebiu  
    OP
       2016-05-06 11:22:36 +08:00
    @zwzmzd 这样没问题。但就像我 14 楼提到的,直接在 vec.reserve(MAX_BUFSIZ)也可以修复问题,静态开一个 vector 相当于调 reserve ,仍然是在堆上分配内存吧。而且 new char[] 在堆上也没问题,应该不是堆/栈的原因。

    顺便纠正之前的几个错误,一个是 64 位 ub 下是 121 字节临界(之前忘减 14 字节 eth 头了),不过反正这个数的绝对值意义不大,也无所谓。
    另一个可能的错误是,之前认为是内存分配的原因,但我自己写了个 allocator ,每次都在全局上 new 内存,问题仍然出现,而且临界大小不变。

    我写的 allocator patch : https://gist.github.com/isofew/2ffa93faf6ebe3899de55b87115e1551

    @colatin
    @kkhaike
    @boydfd
    @linux40

    uint8_t arr[MAX_BUFSIZ] 静态开输出如下:
    alloc 29 byte(s) at 30174048
    &vec[0]: 30174048, arr: 140734511709248
    dealloc 29 byte(s) at 30174048

    alloc 128 byte(s) at 30174208
    &vec[0]: 30174208, arr: 140734511709248
    dealloc 128 byte(s) at 30174208

    new uint8_t[size] 动态开输出如下:
    alloc 29 byte(s) at 21465952
    &vec[0]: 21465952, arr: 21466000
    dealloc 29 byte(s) at 21465952

    alloc 128 byte(s) at 21466160
    &vec[0]: 21466160, arr: 21466304
    dealloc 128 byte(s) at 21466160

    不管哪种开法,都是&vec[0]小包不行大包可以, arr 全都可以。
    aigebiu
        28
    aigebiu  
    OP
       2016-05-06 11:44:32 +08:00
    新发现,如果静态分配 vector 的内存,问题就解决了;如果用 malloc 申请内存,问题也解决了。
    看来问题要变成 new 和 malloc 的区别了 233
    除了调 constructor 外还有什么区别呢?请教楼上诸位了

    输出:
    (static) alloc 29 byte(s) at 140733249528912
    &vec[0]: 140733249528912, buffer: 140733250577904
    dealloc 29 byte(s) at 140733249528912

    malloc 29 byte(s) at 35519328
    &vec[0]: 35519328, buffer: 140731255293104
    free 29 byte(s) at 35519328

    就不传 patch 了,静态的大概是这样:
    value_type pool[1<<20];
    size_type cur = 0;
    pointer allocate(size_type num, const void* = 0)
    {
    pointer p = &pool[cur];
    cur += num;
    std::cerr << "(static) alloc " << num << " byte(s) at " << (size_type)p << std::endl;
    return p;
    }

    动态( malloc )的大概是这样:
    pointer allocate(size_type num, const void* = 0)
    {
    T* p = (T*)(malloc(num * sizeof(T)));
    std::cerr << "malloc " << num << " byte(s) at " << (size_type)p << std::endl;
    return p;
    }
    void deallocate(pointer p, size_type num)
    {
    std::cerr << "free " << num << " byte(s) at " << (size_type)p << std::endl;
    free((void*)p);
    }
    3dwelcome
        29
    3dwelcome  
       2016-05-06 11:49:22 +08:00 via Android
    很负责的告诉楼主、 vector 内存是连续的、不管那个 stl 实现都是如此。

    你的问题最大可能性、是预留空间不足。库里都是指针、没办法帮你修正 vector 数组大小。而你程序外部的修正、很可能把库里记录的地址给冲掉了。
    araraloren
        30
    araraloren  
       2016-05-06 11:56:36 +08:00
    @aigebiu `gcc`的`stl`实现中`vector`本身默认的`allocator`就是简单的`new`操作,而对于内置类型`uint_8`并没有什么构造区别
    aigebiu
        31
    aigebiu  
    OP
       2016-05-06 12:02:19 +08:00
    @araraloren 是,内置类型没有构造一说,所以我在考虑“调 constructor 外还有什么区别”。 27 、 28 楼里提到,在自己实现的 allocator 中,用 new 就有问题,用 malloc 就成功了,说明他俩分到的内存是有区别的。
    aigebiu
        32
    aigebiu  
    OP
       2016-05-06 12:04:47 +08:00
    @3dwelcome 能否详细讲下预留空间不足的问题?为什么同样是小空间,用 malloc 给 vector 开的内存就没问题呢?
    aigebiu
        33
    aigebiu  
    OP
       2016-05-06 12:52:38 +08:00
    @aigebiu
    @araraloren
    @3dwelcome 抱歉 刚才仔细看了下 malloc 也有问题,只有静态分配的没问题。(开了个 pool 数组的那个)
    @zwzmzd 这样看来,可能确实和堆/栈有关。
    araraloren
        34
    araraloren  
       2016-05-06 14:08:48 +08:00
    @aigebiu 那就是了,因为`new`的实现也是简单的调用`malloc`,并没有什么太高级的东西。。
    还有我再次想说的是,不要那么使用`vector`,自己实现一个简单的指针资源管理都比直接用`vector`强。。
    aigebiu
        35
    aigebiu  
    OP
       2016-05-06 14:34:28 +08:00
    @araraloren 嗯是 要不是因为它提供的接口是 vector 我也不愿意这么折腾 自己 malloc free 最方便 谢谢提醒
    neoblackcap
        36
    neoblackcap  
       2016-05-06 14:41:01 +08:00
    @aigebiu 返回 vector 这样的容器很好啊。你 vec 变量生命周期完了那么久自己释放资源了,你 malloc 跟 free 还有可能忘记了。
    yuyang1110
        37
    yuyang1110  
       2016-05-06 18:23:45 +08:00
    @aigebiu 跑个题,其实可以用 vector.data() http://en.cppreference.com/w/cpp/container/vector/data
    linux40
        38
    linux40  
       2016-05-07 09:46:41 +08:00 via Android
    new 并不是简单的 malloc ,这样认为的,自己回去补充知识吧。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2615 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 85ms · UTC 10:53 · PVG 18:53 · LAX 03:53 · JFK 06:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.