Как безопасно обернуть malloc или эмулировать с новым оператором

Существует ли общепринятый безопасный подход к упаковке malloc в функции в C ++? Я пытаюсь выделить блоки памяти произвольного размера для хранения вывода функции, которая записывает двоичные значения в буфер фиксированного размера (т. Е. Машинные инструкции). Подход, который я сейчас использую, выглядит так:

class voidp {
void *p;
public:
voidp (void *pp) : p (pp) {}
template<class T> operator T *() { return (T *) p; }
};

When converting C to C++, you can define malloc like this:

inline voidp
mymalloc (size_t size)
{
return malloc (size);
}
#define malloc mymalloc

Во многих случаях это кажется явным (void*) (по крайней мере, в случае моего компилятора) не разрешены, и я должен использовать основанный на шаблонах подход выше.

Является ли этот подход безопасным, и есть ли какие-либо последствия в отношении «правила трех» (то есть: есть ли какие-либо конструкторы, которые мне нужно отключить или явно определить / перегрузить)?

Спасибо.

Рекомендации

  1. Мой Rant на C ++ новый оператор — пробивая целое в системе типов, Получено 2014-09-05, <http://www.scs.stanford.edu/~dm/home/papers/c++-new.html>
  2. Решите выравнивание памяти в C вопросе интервью, которое поставило меня в тупик, Получено 2014-09-05, <https://stackoverflow.com/questions/227897/solve-the-memory-alignment-in-c-interview-question-that-stumped-me>

редактировать


Причина, по которой я это делаю, заключается в том, что я пытаюсь использовать что-то потенциально лучшее, чем просто выделение блока символов с помощью void* buffer = new char[100], Чтобы уточнить, я пишу код более низкого уровня, который, вопреки моим рекомендациям, должен быть написанным на C ++, а не на чистом C. Мне также необходимо иметь методы динамического выделения памяти, которые создают куски памяти в куче с выравниванием по 16, 32 и 64 байта, как в примере ниже для 16 -байтовое выравнивание.

{
void *mem = malloc(1024+15);
void *ptr = ((uintptr_t)mem+15) & ~ (uintptr_t)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}

Мое приложение буквально создает группу низкоуровневых машинных инструкций в блоках, которые должны быть выровнены 16/32/64 байта (или это вызовет ошибку на уровне процессора), а затем передает их встроенному чипу DSP, который их запускает в качестве реальных инструкций, а не только входных данных.

3

Решение

Тема 1: Старое доброе void* ?

C ++ имеет более сильную типизацию, чем C. Это не проблема, а скорее решение! Таким образом, компилятор перехватывает множество неприятных проблем, которые вам потребуются часы, чтобы выяснить при отладке.

Простой пример Следующий код компилируется в C. Когда я выполняю, я получаю дамп ядра.

FILE *fp;                     // file
void *pbuff;                  // pointer to buffeer
pbuff = malloc(BUFSIZ);
fp=fopen("test.txt", "rw");
fread(fp, BUFSIZ, 1, pbuff);  // ooops !!  Did you notice ?

Зачем ? Я перевернул fp и pbuff. Оба являются жертвами void* в anything* а также anything* в void* строит. Конечно, опытные программисты не допускают таких ошибок в стандартных библиотеках! Но что с остальными?

В C ++ этот ошибочный код не компилируется, потому что преобразование void* чёрт побери FILE* признается проблемой

Тема 2: Является ли шаблон voidp безопасной практикой?

Ну, в C ++ есть классы и наследование. И целая куча приведений, чтобы выделить странные операции с указателями, которые вы могли (и все еще можете) делать. Снова C ++ облегчает поиск ошибок. Например, reinterpret_cast<> заслуживает большего внимания, чем более контролируемый static_cast<>

Чтобы делать безопасные вещи, нужно понимать объектную модель C ++ и использовать соответствующие инструменты. Ваш voidp() очень умный, но не подходит для ООП.

Простой пример, сначала родной путь:

   struct A { std::string name; A(std::string s) : name(s) {} };
struct B : A { int x, y;  B(std::string s, int a, int b) : A{ s }, x(a), y(b) {}  };
...
A a{ "Chris" };
B b{ "Tophe", 30, 40 };
A *gpa = &a;
B *gpb = &b;
gpa = gpb;    // yes, works, because a B is an A and compiler knows it.
//gpb = gpa;  // No ! you can't without a recast !

Теперь с вашим voidp подход:

voidp pa(&a);
voidp pb(&b);
pb = pa;  // no problem.
gpb = pa;  // no problem ! Not even a cast to draw attention on potential issue !

Итак, вы видите, это довольно небезопасно! Это действительно скрывает неприятные ошибки!

Тема 3: Есть malloc() лучше чем new ?

Честно, с malloc() Вы можете легко создать что угодно. И так же легко создать что-то неправильного размера …. С newВы также можете делать странные вещи, но гораздо сложнее делать основные ошибки.

new Можно вызвать конструктор объекта. С malloc() вам нужны дополнительные шаги, чтобы сделать это.

Затем, когда у вас есть malloc() у тебя есть free(), malloc()/free() отлично подходят для пассивных структур данных, которые у вас есть в C. Но в C ++, когда вы хотите избавиться от объекта должным образом, вы должны это сделать. Так delete действительно более уместно.

Заключение

Я прочитал вашу ссылку с интересом. Это правда, что new имеет ограничения.

Но я не разделяю выводы статьи. Вместо того, чтобы избегать new и переключиться обратно на malloc() (опять же: он идеально подходит для кода C, но просто не подходит для C ++), это был бы лучший способ исследовать стандартную библиотеку и, например, использовать shared_ptr<> и другие умные указатели. Это намного лучше для предотвращения утечек памяти, чем переписывание одной собственной версии malloc …

редактировать

Конкретное использование, которое вы делаете, может быть сделано и в C ++

char buffer[1024 + 15];     // allocation  on stack, or char *mem = new char[1024+15]
char *ptr = reinterpret_cast<char*>(((uintptr_t)&buffer + 15) & ~(uintptr_t)0x0F);   // Yes
std::fill_n(ptr, 0, 1024);     // is memset_16aligned() really portable ?
// nothing if on stack         // or delete[] mem if new was used

Существует также функция std::align() это делает расчет, который вы делаете для ptr.
И есть новое размещение для размещения объекта C ++ по фиксированному адресу, например:

char* p = new(ptr)char[1024];  // of course you shouldn't delete this one
2

Другие решения

Если вы пишете код C ++. Вы должны использовать новый и удалить. Написание стиля C в C ++, как правило, плохая практика, так как компилятор C ++ не будет создавать оптимальные двоичные файлы. Если вы обнаружите, что выполняете много типов при помощи void *, вы, вероятно, делаете это неправильно.

И наоборот, используйте умные указатели C ++ 11!

1

new а также delete для разработки библиотек низкого уровня.
Есть контейнеры STL, доступные для распределения и отмены размещения буфера.

В вашем случае вы можете попробовать

std::vector<unsigned char> myBuffer(MAX_SIZE);
0