2013-08-21 3 views
39

Поскольку наше приложение имеет жесткие ограничения производительности и памяти, наши стандарты кодирования запрещают использование кучи по умолчанию —, т. Е. Нет malloc, по умолчанию new. Каждое распределение памяти должно выбирать один из нескольких конкретных распределителей; что-то вродеМожно ли полностью отключить новый оператор C++ по умолчанию?

// declared globally 
void* operator new(size_t size, CustomAllocHeap* heap, const char* perpetrator_name) 
{ 
    return heap->Allocate(size, perpetrator_name); 
} 
// imagine a bunch of CustomAllocHeap's declared globally or statically, thus 

Vector* v = new(gPhysicsHeap, __FUNCTION__) Vector(1.0f, 2.0f, 3.0f, 4.0f); 
// or in a class 
Thingy* p = new(this->LocalArenaHeap, __FUNCTION__) Thingy(); 

Хотя мы поддерживали хорошую дисциплину на этом с нашим кодом, некоторые стандартные C++ компоненты (контейнеры, std::function) скрытно выполнять вызовы по умолчанию new динамической памяти, что очень плохо.

Было бы хорошо отключить по умолчанию new в некотором роде, так что любая строка кода, которая неявно приводит к распределению по умолчанию, сразу бросает ошибку компилятора. Это позволит нам сразу заметить эти вещи.

Мы можем, очевидно, сделать new бросать ошибки во время выполнения т.е.

void* operator new (size_t) { __debugbreak(); return NULL; } 

, но было бы гораздо лучше, чтобы получать предупреждения об этом во время компиляции. Это возможно?

Наше приложение создано для стационарной платформы (x64 с Visual Studio); мобильность не имеет значения.

+2

Вы пробовали смотреть на выходе линкера? Я бы подумал, что вы сможете указать на выходе компоновщика компоновки, вызываются ли подпрограммы распределителя. Это не указывает на оскорбительную линию, но, по крайней мере, она сможет сломать время сборки. –

+0

интересный указатель там: http://bytes.com/topic/c/answers/854450-there-any-way-disable-global-operator-new – SirDarius

+2

- это возможность реализовать стандартные (все формы) новые/удалить с точки зрения вашего нового? Кажется, я помню, что можно экспортировать new/delete с помощью .def-файла, в результате чего (весь?) Процесс используется с экспортированной версией. – stijn

ответ

17

Вы можете реализовать по умолчанию new, чтобы вызвать нереализованную функцию. Затем, во время компоновки, вы получите сообщение об ошибке для пользователей голого new вызова:

#include <stdexcept> 
inline void * operator new (std::size_t) throw(std::bad_alloc) { 
    extern void *bare_new_erroneously_called(); 
    return bare_new_erroneously_called(); 
} 

Когда я тестировал на IDEONE, я получил эту ошибку:

/home/geXgjE/ccrEKfzG.o: In function `main': 
prog.cpp:(.text.startup+0xa): undefined reference to `bare_new_erroneously_called()' 
collect2: error: ld returned 1 exit status 

В моих тестах, используя g++, нет ошибки ссылки, если в программе нет ссылок на голый new. Это связано с тем, что g++ не испускает код для неиспользованных функций inline.

У меня нет Visual Studio, установленной в моей системе, поэтому следующая информация основана только на некоторой документации, которую я нашел. Чтобы заставить оператора inline new увидеть всюду, вы должны поместить его определение в файл заголовка, а затем использовать опцию /FI detect_bare_new.h в своем компиляторе. * Согласно this answer, Visual Studio не будет генерировать код для неиспользованных inline функций (например, g++). Однако вы должны проверить, есть ли уровень оптимизации, который должен быть включен для этого поведения или нет.

* g++ has a similar compiler option: -include detect_bare_new.h .

Предполагается, что вы намерены передать свои собственные распределители шаблонам и классам C++ в стандартной библиотеке C++. Если вы этого не сделаете, то встроенный код в стандартных заголовках, которые вызывают распределитель по умолчанию (который вызовет new), также вызовет ошибку связывания. Если вы хотите, чтобы стандартная библиотека C++ использовала по умолчанию new, тогда простой способ заставить ее работать (за счет более длительных времени компиляции) - добавить все стандартные заголовки C++, которые вы собираетесь включить в начало detect_bare_new.h файл.

Вы заявляете, что переносимость решения для вас не важна.Но для полноты, я должен подчеркнуть вопрос, который Бен Фойгт правильно указывает: стандарт C++ не гарантирует поведение не генерирующего кода для неиспользуемых функций inline. Таким образом, можно получить ошибку связывания, даже если функция не используется. Но, если код не имеет других ссылок на нереализованную функцию, кроме только в пределах заштрихованной реализации new, ошибка будет в самом определении new. Например, g++ может генерировать ошибку, как:

/home/QixX3R/cczri4AW.o: In function `operator new(unsigned int)': 
prog.cpp:(.text+0x1): undefined reference to `bare_new_erroneously_called()' 
collect2: error: ld returned 1 exit status 

Если ваша система один, который генерирует код для неиспользуемых inline функций, вы можете все еще есть обходной путь. Обходной путь будет работать, если компоновщик сообщит все ошибочные ссылки на неопределенную функцию. В этом случае, если наблюдаемая единственная ошибка связывания связана с определением самого оператора new, неожиданных звонков на голой new нет. После проверки того, что код имеет только одну ошибку, вы могли бы изменить линию ссылок, чтобы включить объект или библиотеку, у которой есть соответствующее определение bare_new_erroneously_called(), которое будет генерировать исключение во время выполнения.

+0

Аккуратный трюк, мне нужно помнить, что :-) – cmaster

+0

Вы уверены, что это законно в соответствии со стандартом? [Ответы на этот вопрос предполагают, что он не может быть законным] (http://stackoverflow.com/q/7297158/335858). – dasblinkenlight

+4

Поскольку 'operator new' не является шаблоном, это вызовет ошибку, вызвана ли она или нет. http://ideone.com/3KwAya –

1

Если ваш собственный «новый» оператор не назвал «новым», но по-разному (например, «myNew»), вы можете использовать «#define» в пути, заменив слова «новый» мусором:

#define new *+-/& 

предварительно компилятор теперь будет заменить «новый»:

x = new mytype; 

по мусору:

x = *+-/& mytype; 

преимущества по сравнению до тез sage при времени связывания заключается в том, что это немедленно генерирует сообщение компилятора при компиляции файла C++, а не в конце при связывании. Вы также видите линию, в которой находится «новое».

Недостаток заключается в том, что вам придется «#include» файл, содержащий этот «#define» во всех файлах на C++ в вашем проекте.

+2

Я не знаю, будет ли это работать при связывании с предварительно скомпилированными библиотеками –

+0

@DanF: Обычно стандарты кодирования не распространяются на сторонние библиотеки, предоставленные в предварительно скомпилированной форме. Не-встроенная версия моего предложения (и расширение его, которое охватывает «malloc()» и «друзей»), может определить, использует ли библиотека динамическое распределение, но единственным решением было бы переопределить 'malloc()' и ' new() 'с разным поведением или перекомпилировать измененные версии библиотек-нарушителей. – jxh

+1

Но это также сломает 'new (g_SpecificAllocator) Thingy()', который мы * делаем *. – Crashworks

0

Poison it!
Если вы используете GCC, есть прагма для этого:

#ifdef __GNUC__ 

/* poision memory functions */ 
# pragma GCC poison malloc new 

#endif 
Смежные вопросы