aigebiu
V2EX  ›  C

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

By aigebiu at 2016 年 5 月 5 日 · 4857 次点击

用 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 年 5 月 5 日
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 年 5 月 5 日
去掉 const 重新编译了下 nfqueue 库,一样,还是 135 字节以上就行。应该和 nfqueue 库无关,是 vector 的问题。
kernel 4.4.0 ubuntu 14.04.01 x64
第 3 条附言  ·  2016 年 5 月 5 日
编译到树莓派 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 年 5 月 6 日
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 年 5 月 6 日
但数组不管是静态开的,还是 malloc/new 的,都没问题。
第 6 条附言  ·  2016 年 5 月 6 日

我有点明白了…… 如下,删掉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 年 5 月 6 日

额……大家散了吧……

原因是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 年 5 月 6 日
更正:不一定每次新开大块内存,取决于具体情况。

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 年 5 月 5 日
vector 的内部不一定是连续的。
Monad
   2
Monad  
   2016 年 5 月 5 日   ❤️ 1
反对楼上 摘自 N3690.pdf
没用过 libnetfilter_queue, 楼主你的问题我猜测是 size 有问题...
araraloren
   3
araraloren  
   2016 年 5 月 5 日
不要这么用 vector 以及其它模板, C 接口就老老实实使用 C 数据结构。。
kkhaike
   4
kkhaike  
   2016 年 5 月 5 日
我也觉得是 size 有问题,你把他替换成 vec.size()试试
aigebiu
   5
aigebiu  
OP
   2016 年 5 月 5 日
@Monad @kkhaike size=vec.size(); 抱歉忘说了

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

···
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 年 5 月 6 日 via Android
@zwzmzd &vec[0]和 arr[]没区别是建立在你用的 Alloc 是 std::allocator ,标准没有说 Alloc::pointer 一定是指针(T*)哟,标准也没有说&t 的返回值一定是指针哟。
zwzmzd
   26
zwzmzd  
   2016 年 5 月 6 日
@linux40 是指 operator& 重载么? LZ 遇到的应该不是这个问题吧
aigebiu
   27
aigebiu  
OP
   2016 年 5 月 6 日
@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 年 5 月 6 日
新发现,如果静态分配 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 年 5 月 6 日 via Android
很负责的告诉楼主、 vector 内存是连续的、不管那个 stl 实现都是如此。

你的问题最大可能性、是预留空间不足。库里都是指针、没办法帮你修正 vector 数组大小。而你程序外部的修正、很可能把库里记录的地址给冲掉了。
araraloren
   30
araraloren  
   2016 年 5 月 6 日
@aigebiu `gcc`的`stl`实现中`vector`本身默认的`allocator`就是简单的`new`操作,而对于内置类型`uint_8`并没有什么构造区别
aigebiu
   31
aigebiu  
OP
   2016 年 5 月 6 日
@araraloren 是,内置类型没有构造一说,所以我在考虑“调 constructor 外还有什么区别”。 27 、 28 楼里提到,在自己实现的 allocator 中,用 new 就有问题,用 malloc 就成功了,说明他俩分到的内存是有区别的。
aigebiu
   32
aigebiu  
OP
   2016 年 5 月 6 日
@3dwelcome 能否详细讲下预留空间不足的问题?为什么同样是小空间,用 malloc 给 vector 开的内存就没问题呢?
aigebiu
   33
aigebiu  
OP
   2016 年 5 月 6 日
@aigebiu
@araraloren
@3dwelcome 抱歉 刚才仔细看了下 malloc 也有问题,只有静态分配的没问题。(开了个 pool 数组的那个)
@zwzmzd 这样看来,可能确实和堆/栈有关。
araraloren
   34
araraloren  
   2016 年 5 月 6 日
@aigebiu 那就是了,因为`new`的实现也是简单的调用`malloc`,并没有什么太高级的东西。。
还有我再次想说的是,不要那么使用`vector`,自己实现一个简单的指针资源管理都比直接用`vector`强。。
aigebiu
   35
aigebiu  
OP
   2016 年 5 月 6 日
@araraloren 嗯是 要不是因为它提供的接口是 vector 我也不愿意这么折腾 自己 malloc free 最方便 谢谢提醒
neoblackcap
   36
neoblackcap  
   2016 年 5 月 6 日
@aigebiu 返回 vector 这样的容器很好啊。你 vec 变量生命周期完了那么久自己释放资源了,你 malloc 跟 free 还有可能忘记了。
yuyang1110
   37
yuyang1110  
   2016 年 5 月 6 日
@aigebiu 跑个题,其实可以用 vector.data() http://en.cppreference.com/w/cpp/container/vector/data
linux40
   38
linux40  
   2016 年 5 月 7 日 via Android
new 并不是简单的 malloc ,这样认为的,自己回去补充知识吧。
© 2026 V2EX · 50ms · 3.9.8.5