4

C 编程中的一种常见模式涉及可变长度结构,例如:

typedef struct {
    int length;
    char data[1];
} MyBuffer;

其中 data 并不是 [1] 的数组。相反,它的可变长度由length.

结构分配如下:

MyBuffer* pBuff = malloc(sizeof(MyBuffer) + 100);

我想使用相同的模式,但在 C++ 代码中,所以使用new/delete而不是malloc/free

可以在 C++ 代码中使用相同的模式吗?如何?

编辑由于几个答案和评论建议我切换到std::vector

MyBuffer从第 3 方 C 库中获得了结构定义。
在我的 C++ 应用程序中,我需要分配缓冲区并调用 C 库中的函数。

在边界的“我这边”,我更喜欢保留 C++,并以 C++ 方式分配这个结构,但我仍然需要将它传递给一个不会理解诸如std::vector.

4

7 回答 7

6

如果您需要保持与您正在使用的现有 C 代码的兼容性,那么它可以与 C++ 一起使用,几乎没有变化(只需将 return from 强制转换malloc())。

#include <stdlib.h>

typedef struct {
    int length;
    char data[1];
} MyBuffer;

void f() {

    MyBuffer* pBuff = (MyBuffer *)malloc(sizeof(MyBuffer) + 100);
}

这使用 g++ 编译没有问题。

如果您担心管理分配给我的内存,malloc()那么您可以创建一个类来管理它并MyBuffer通过成员方法公开指针,例如:

std::shared_ptr<MyBuffer> buf((MyBuffer *)malloc(sizeof(MyBuffer) + 100), free);

这很麻烦,我承认...

于 2013-09-06T15:56:59.080 回答
5

这在 C++ 中不是惯用的,也不是必需的。该语言提供std::vector<unsigned char>了为您包装大小和缓冲区的语言,或者如果您不需要在运行时动态调整大小,C++11 提供了std::array<unsigned char>.

编辑:这里要注意的关键是不要vector在堆上自行分配!如果您将向量按值放在堆栈或另一个对象中并在构造时正确调整其大小,您将使用与 C 版本(一个)完全相同的分配数量。但是您将使用惯用语言功能并防止自己制造各种内存错误和/或泄漏。

于 2013-09-06T16:02:24.260 回答
3

我认为这可以解决问题,给出或接受一些语法错误。

class MyBuffer {
 // Important, this class must not have any virtual methods.
 public:
  void* operator new(size_t data_length) {
    MyBuffer* buffer = static_cast<MyBuffer>(new char[sizeof(MyBuffer) + data_length]);
    buffer->length = data_length;
    return buffer;
  }

 private:
  int length;
  char data[1];
};

编辑:

这种技术的一个主要缺点是,调试构建覆盖全局操作符 new 是相当常见的,它为缓冲区溢出和内存泄漏提供运行时检查。我不确定这将如何与全局运算符 new 的非标准实现交互。

于 2013-09-06T16:47:25.493 回答
2

您可以在 C++ 中使用模板,这是 C 所缺乏的功能。模板参数确定数组的大小。

template <unsigned N>
struct MyBufferTemplate {
    int length;
    char data[N];
};

N在编译时未知的情况下,您可以定义一些合理大小的值,并执行最佳拟合分配。

但是,如果这似乎太浪费内存,那么另一种方法是定义一个接口std::vector<int>(这是基于 MadScienceDreams 的评论)。

struct MyBufferAdapter {
    MyBufferAdapter (int databytes = 1)
        : buf_(1 + (databytes+sizeof(int))/sizeof(int)) { buf_[0] = databytes; }
    void resize (int newdatabytes) {
        int newlength = 1 + (newdatabytes+sizeof(int))/sizeof(int);
        buf_.resize(newlength);
        buf_[0] = newdatabytes;
    }
    int & length () { return buf_[0]; }
    int length () const { return buf_[0]; }
    char * data () { return static_cast<char *>(&buf_[1]); }
    const char * data () const { return static_cast<const char *>(&buf_[1]; }
    operator MyBuffer * () { return reinterpret_cast<MyBuffer *>(&buf_[0]); }
    operator const MyBuffer * () const {
        return reinterpret_cast<const MyBuffer *>(&buf_[0]);
    }
private:
    std::vector<int> buf_;
};
于 2013-09-06T16:09:01.107 回答
1

就个人而言,我会选择mallocand free。但是,您可以全力以赴new[],放置newdelete[]

#include <new>

struct MyBuffer {
    int length;
    char data[1];
};

MyBuffer* make_a_buffer(int size)
{
    // allocate buffer large enough for what we want
    char* raw_memory = new char[sizeof(MyBuffer) + size];

    // call placement new to put a MyBuffer in the raw memory
    MyBuffer* buffer = new (raw_memory) MyBuffer;
    buffer->length = size;
    return buffer;
}

void destroy_a_buffer(MyBuffer* buffer)
{
    // in this case, MyBuffer has a trivial (default) destructor, so this isn't
    // really needed, but in other cases you may need to call the
    // destructor
    //
    // NOTE: there is placement new, but no placement delete
    // this is the only way to correctly destroy the object
    buffer->~MyBuffer();

    // we've destroyed the object, and now we need to release the
    // memory, luckily we know we got it from new[], so we can
    // delete[] it
    delete[] static_cast<char*>(static_cast<void*>(buffer));
}
于 2013-09-06T16:52:25.227 回答
1

如果您一定要使用 C 结构,但想在 C++ 中使用更好的方法,您可以结合使用模板和继承:

#include <iostream>
#include <memory>
#include <stdlib.h>

// Here's your C struct.
// Old C-style usage would be:  MyBuffer* pBuff = malloc(sizeof(MyBuffer) + 100);
// Which effectively gives you a 101-byte array for 'data'.
// (1 for the original array, +100).
typedef struct {
    int length;
    char data[1];
} MyBuffer;

// This defines a generic template that inherits from whatever you want, and
// adds some padded space to the end of it.  The 'extra_bytes' is equivalent
// to the '+100' you used to do in the c-style malloc trick (i.e. this still
// allocates a 101-byte array for 'data').
template<typename T, size_t extra_bytes>
struct ExtraBytes : public T {
  char padding[extra_bytes];
};

// If you just want to wrap your one struct, you can use this.  The constructor
// even sets the length for you.
template<size_t array_size>
struct MyBufferWrapper : public MyBuffer {
  char padding[array_size - 1];  // 1 is already allocated to 'data'
  MyBufferWrapper() {
    length = array_size;
  }
};

int main(int, char**) {
  MyBuffer normal;
  std::cout << "Sizeof normal MyBuffer = " << sizeof(normal) << "\tlength = "
         << normal.length << "\n";  // length is uninitialized

  MyBuffer* pBuff = static_cast<MyBuffer*>(malloc(sizeof(MyBuffer) + 100));
  std::cout << "Sizeof malloc'd MyBuffer = " << sizeof(*pBuff) << "\tlength = "
         << pBuff->length << "\n";  // length is uninitialized

  ExtraBytes<MyBuffer, 100> extra_bytes;
  std::cout << "Sizeof templated ExtraBytes = " << sizeof(extra_bytes)
         << "\tlength = " << extra_bytes.length << "\n";  // length is uninitialized

  MyBufferWrapper<100> wrapper;
  std::cout << "Sizeof Wrapped MyBuffer = " << sizeof(wrapper)
         << "\tlength = " << wrapper.length << "\n";  // length is set to 100

  // If you reall  auto heap = std::make_shared<MyBufferWrapper<100>>();
  auto heap = std::make_shared<MyBufferWrapper<100>>();
  std::cout << "Sizeof heap-allocated Wrapper = " << sizeof(*heap)
         << "\tlength = " << heap->length << "\n";  // length is 100

  return 0;
}

请注意,使用这种方法,您不需要使用malloc/free nor new/delete。你只需MyBufferWrapper用你想要的任何数组大小声明你的,它被分配到堆栈上,然后你使用它(你可以把它当作一个普通的MyBuffer)。如果你使用堆分配的内存,你可以使用std::unique_ptror std::shared_ptr

于 2013-09-06T18:10:32.207 回答
1

解决此问题的一种 C++“ish”方法是将缓冲区本身描述为“可简单复制”(C++11 术语,在 C++98 和 2003 中是“普通旧数据”的“POD”)结构,其中微观例外是它有一个私有构造函数来防止实例化。然后为该结构构造一个指针对象。这是一个带有这个想法的完整但微不足道的程序:

#include <cstdlib>
#include <cstring>

struct MyBuffer
{
    int length;
    char data[1];
private:
    MyBuffer() {}
    MyBuffer& operator =(MyBuffer& other) { return other; }
};

class MyBufferPointer
{
    MyBuffer *bufptr_;

    static std::size_t getsize(std::size_t array_size)
    {
        return sizeof (MyBuffer) + array_size * sizeof (char);
    }

    static MyBuffer *getbuf(std::size_t array_length)
    {
        std::size_t sz = getsize(array_length);
        return static_cast<MyBuffer*>( malloc(sz) );
    }

public:
    MyBufferPointer() { bufptr_ = NULL; }

    MyBufferPointer(std::size_t array_length)
    {
        bufptr_ = getbuf(array_length);
        bufptr_->length = array_length;
    }

    MyBufferPointer(const MyBufferPointer &other)
    {
        const MyBuffer *op = other.bufptr_;
        if (op == NULL)
        {
            bufptr_ = NULL;
        }
        else
        {
            bufptr_ = getbuf(op->length);
            bufptr_->length = op->length;
            std::size_t sz = op->length * sizeof op->data[0];
            std::memmove( bufptr_->data, op->data, sz );
        }
    }

    MyBufferPointer& operator =(const MyBufferPointer &other)
    {
        const MyBuffer *op = other.bufptr_;
        if (op == NULL)
        {
            bufptr_ = NULL;
        }
        else
        {
            bufptr_ = getbuf(op->length);
            bufptr_->length = op->length;
            std::size_t sz = op->length * sizeof op->data[0];
            std::memmove( bufptr_->data, op->data, sz );
        }
        return *this;
    }

    ~MyBufferPointer() { if (bufptr_) free(bufptr_); }

    std::size_t size() const
    {
        return bufptr_ ? bufptr_->length : 0;
    }

    // conventience operations for access to the data array:
    char &operator [](std::size_t index) { return bufptr_->data[index]; }
    char at(size_t index) const { return bufptr_->data[index]; }
    MyBuffer* c_buffer() { return bufptr_; }
};

#include <iostream>
using namespace std;

int main()
{
    MyBufferPointer bufp;
    cout << "bufp().size() = " << bufp.size()
         << ", c_buffer=" << bufp.c_buffer() << endl;

    bufp = MyBufferPointer(100);
    cout << "bufp().size() = " << bufp.size()
         << ", c_buffer=" << bufp.c_buffer() << endl;
    return 0;
}

MyBuffer 结构是 C 数据区域的布局,只有私有构造函数和赋值运算符声明以防止实例化或尝试复制(在 C 或 C++ 中,它们都不能正常工作。) MyBufferPointer 类将其封装为 C++样式 char[] 数组,重载 [] 运算符。

这仍然使用 malloc(),而不是新的。满足您提到的那些 C API 所需的内存映像需要可变长度结构,而您无法在 new 创建的标准 C++ 类中获得它。这只是提供了一个 C++ 包装器,用于在该类中提供一个结构创建点(在静态成员函数 getsize() 和 getbuf() 中);并保证在指针超出范围时删除缓冲区。您可以添加 resize()、to_string()、substring() 或任何您想要的方法。

在优化之后,性能应该与普通指针访问的 C 结构相同,因为方法是在类中声明的并且足够简单,可以内联。

于 2013-09-06T19:11:10.830 回答