memcpy 没有复制到缓冲区

Posted

技术标签:

【中文标题】memcpy 没有复制到缓冲区【英文标题】:memcpy not copying into buffer 【发布时间】:2015-01-05 02:02:09 【问题描述】:

我有一个带有 std::vector<unsigned char> mPacket 的类作为数据包缓冲区(用于发送 UDP 字符串)。有一个对应的成员变量mPacketNumber,用来记录到目前为止已经发送了多少包。

我在课堂上做的第一件事就是预留空间:

mPacket.reserve(400);

然后,在我希望发送数据包时运行的循环中:

mPacket.clear(); //empty out the vector
long packetLength = 0; //keep track of packetLength for sending udp strings

memcpy(&mPacket[0], &&mPacketNumber, 4); //4 bytes because it's a long
packetLength += 4; //add 4 bytes to the packet length

memcpy(&mPacket[packetLength], &data, dataLength);
packetLength += dataLength;

udp.send(mPacket.data(), packetLength);

除了我意识到什么都没有发送!多么奇特。

所以我挖得更深了,发现mPacket.size() 返回零,而packetLength 返回我认为数据包应该是的大小。

我想不出mPacket 长度为零的原因——即使我对数据处理不当,带有mPacketNumber 的标头也应该写得很好。

谁能告诉我为什么会遇到这个问题?

谢谢!

【问题讨论】:

reserveresize 不同。 哦,嗯。所以reserve 为我保留了内存,但没有实际调用push_back,向量不“知道”有这个数据推送给它吗?有趣的。所以我的选择是 resize 向量,以便它知道它可以读取内存中的相邻数据? 你在做什么你认为会导致数据包长度不为零? 好吧,我认为将字节物理复制到为向量保留的内存中就可以了。显然,我错了(因为我不完全了解矢量的工作原理)。没必要对此嗤之以鼻。 @nathanlachenmyer “保留内存”和“创建元素”之间的区别是为了支持具有构造函数或复制构造函数或析构函数的非平凡类型。虽然您在这里不使用这种类型,但界面仍然需要尊重每种类型。 【参考方案1】:

您保留的元素不用于正常使用。仅当您调整矢量大小时才会创建元素。虽然它可能看起来可以正常工作,但对于具有构造函数的类型来说,情况会有所不同——你可以看到构造函数没有被调用。这是未定义的行为 - 您正在访问在这种情况下不允许的元素。

.reserve() 操作通常与.push_back() 一起使用以避免重新分配,但这里不是这种情况。

如果您使用.reserve(),则不会修改.size()。你应该改用.resize()


或者,您可以将复制操作与.push_back().reserve() 一起使用,但您需要放弃使用memcpy,而是将std::copystd::back_inserter 一起使用,后者使用@987654331 @ 将元素推送到另一个容器:

std::copy(reinterpret_cast<unsigned char*>(&mPacketNumber), reinterpret_cast<unsigned char*>(&mPacketNumber) + sizeof(mPacketNumber), std::back_inserter(mPacket))
std::copy(reinterpret_cast<unsigned char*>(&data), reinterpret_cast<unsigned char*>(&data) + dataLength, std::back_inserter(mPacket));

这些reinterpret_casts 很烦人,但代码仍然有一个优势——如果您的估计太低,您不会出现缓冲区溢出。

【讨论】:

std::back_inserter 与 reinterpret cast 会在将longs 分解为 4 个不同的无符号字符并将它们推回方面做正确的事情吗? @nathanlachenmyer 它会做你现在正在做的同样的事情(例如,将它们按顺序复制到目标缓冲区,但使用.push_back)。这段代码不考虑字节顺序,但你的也不考虑。 谢谢!我会试一试;这可能是最好的解决方案。【参考方案2】:

vector,显然,当您调用 size() 时,不计算元素。向量内部有一个 counter 变量来保存该信息,因为向量分配了大量内存,并且无法真正知道数据的末尾在哪里。当您使用 vector 对象的方法添加/删除元素时,它会更改计数器变量,因为它们被编程为这样做。

您将数据直接添加到其数组指针中,这不会唤醒您的矢量对象的任何反应,因为它不使用任何方法。数据在那里,但向量不承认它,所以计数器保持在 0 并且size() 返回 0。

您应该将所有 size() 调用替换为 packageLength,或者使用向量中的方法来添加/删除/读取数据,或者使用动态分配的数组而不是向量,或者创建自己的包含数组的类并以您喜欢的方式管理它。老实说,在这种情况下使用vector 并没有什么意义。

Vector 是一个传统的高级面向对象组件,在大多数操作系统中,它应该以这种方式使用。

自己的Array类示例:

如果您使用自己的动态分配数组,则必须始终记住它的长度才能使用它。因此,让我们创建一个类来减少我们的懈怠。这个例子有基于memcpy 的元素转移,[] 符号完美地工作。它有一个原始的最大长度,但在必要时会自行扩展。

另外,这是一个内联类。某些 IDE 可能会要求您将其实际分离到头文件和源文件中,因此您可能必须自己这样做。

如有必要,请自行添加更多方法。应用此功能时,不要使用 memcpy 除非您要手动更改 arraySize 属性。您已经集成了 addFromaddBytesFrom 方法,它们在内部使用 memcpy(假设调用数组是目标)并单独增加 arraySize。如果你确实想使用 memcpy,setSize 方法可以用于强制新的数组大小而不修改数组。

#include <cstring>

//this way you can easily change types during coding in case you change your mind
//more conventional object-oriented method would use templates and generic programming, but lets not complicate too much now
typedef unsigned char type;

class Array  
private:
    type *array;
    long arraySize;
    long allocAmount; //number of allocated bytes
    long currentMaxSize; //number of allocated elements

    //private call that extends memory taken by the array
    bool reallocMore()
    
        //preserve old data
        type *temp = new type[currentMaxSize];
        memcpy(temp, array, allocAmount);

        long oldAmount = allocAmount;

        //calculate new max size and number of allocation bytes
        currentMaxSize *= 16;
        allocAmount = currentMaxSize * sizeof(type);

        //reallocate array and copy its elements back into it
        delete[] array;
        array = new type[currentMaxSize];
        memcpy(array, temp, oldAmount);

        //we no longer need temp to take space in out heap
        delete[] temp;

        //check if space was successfully allocated
        if(array) return true;
        else return false;
    

public:
    //constructor
    Array(bool huge)
    
        if(huge) currentMaxSize = 1024 * 1024;
        else currentMaxSize = 1024;

        allocAmount = currentMaxSize * sizeof(type);

        array = new type[currentMaxSize];
        arraySize = 0;
    

    //copy elements from another array and add to this one, updating arraySize
    bool addFrom(void *src, long howMany)
    
        //predict new array size and extend if larger than currentMaxSize

        long newSize = howMany + arraySize;

        while(true)
        
            if(newSize > currentMaxSize)
            
                    bool result = reallocMore();
                    if(!result) return false;
            
            else break;
        

        //add new elements

        memcpy(&array[arraySize], src, howMany * sizeof(type));
        arraySize = newSize;

        return true;
    

    //copy BYTES from another array and add to this one, updating arraySize
    bool addBytesFrom(void *src, long byteNumber)
    
        //predict new array size and extend if larger than currentMaxSize

        int typeSize = sizeof(type);
        long howMany = byteNumber / typeSize;
        if(byteNumber % typeSize != 0) howMany++;

        long newSize = howMany + arraySize;
        while(true)
        
            if(newSize > currentMaxSize)
            
                    bool result = reallocMore();
                    if(!result) return false;
            
            else break;
        

        //add new elements

        memcpy(&array[arraySize], src, byteNumber);
        arraySize = newSize;

        return true;
    

    //clear the array as if it's just been made
    bool clear(bool huge)
    
        //huge >>> 1MB, not huge >>> 1KB
        if(huge) currentMaxSize = 1024 * 1024;
        else currentMaxSize = 1024;

        allocAmount = currentMaxSize * sizeof(type);

        delete[] array;
        array = new type[currentMaxSize];
        arraySize = 0;
    

    //if you modify this array out of class, you must manually set the correct size
    bool setSize(long newSize) 
        while(true)
        
            if(newSize > currentMaxSize)
            
                    bool result = reallocMore();
                    if(!result) return false;
            
            else break;
        

        arraySize = newSize;
    

    //current number of elements
    long size() 
        return arraySize;
    

    //current number of elements
    long sizeInBytes() 
        return arraySize * sizeof(type);
    

    //this enables the usage of [] as in yourArray[i]
    type& operator[](long i)
    
        return array[i];
    
;

【讨论】:

公平积分!您如何建议使用动态分配的数组? 好吧,它就像所有其他数组一样,只是 sizeof(array) 只返回指针大小。我将在 C++ 中编写一个内联类,以便更方便地处理动态分配的数组并将其添加到我的答案中。【参考方案3】:
mPacket.reserve();
mPacket.resize(4 + dataLength); //call this first and copy into, you can get what you want
mPacket.clear(); //empty out the vector
long packetLength = 0; //keep track of packetLength for sending udp strings

memcpy(&mPacket[0], &&mPacketNumber, 4); //4 bytes because it's a long
packetLength += 4; //add 4 bytes to the packet length

memcpy(&mPacket[packetLength], &data, dataLength);
packetLength += dataLength;

udp.send(mPacket, packetLength);

【讨论】:

我看不出有任何理由打电话给reserveclear,因为你打电话给resize 并且整个内容将被覆盖。此外,没有接受零参数的 reserve 重载。 std::vector::reserve 没有零参数,那是我的错!但是我记得resize的增长只是通过push_back,所以它有多个mem alloc,所以我之前调用了reserve。

以上是关于memcpy 没有复制到缓冲区的主要内容,如果未能解决你的问题,请参考以下文章

如何提高 memcpy 的性能

memcpy 是可简单复制的类型构造还是赋值?

复制时跳过 x 字节的 memcpy 等效项(不仅仅是初始 x 字节)?

将字节缓冲区内容复制到结构中

克隆 CVPixelBuffer - 如何?

编写一个C语言的内存拷贝函数,把源地址的指定长度的数据拷贝到目标地址,考虑8,16,32位数据位宽