Макрос для замены оператора C ++ new - PullRequest
25 голосов
/ 06 марта 2009

Можно ли создать макросы для замены всех форм operator new перегрузками, которые включают дополнительные аргументы ... скажем, __FILE__ и __LINE__?

Проблема заключается в том, что operator new может быть закодирован с круглыми скобками или без них, поэтому:

  • объектоподобные макросы:

    #define new new(__FILE__, __LINE__)
    

    заменит объявления типа:

    A* a = new A();
    
  • и макросы, подобные :

    #define new(A) new (A, __FILE__, __LINE__)
    

    заменит объявления типа:

    A* a = new(std::nothrow) A();
    

К сожалению, попытка объявить два макроса с одинаковым идентификатором , даже если они имеют разные типы , является ошибкой:

#define new new(__FILE__, __LINE__)
#define new(A) new (A, __FILE__, __LINE__) // Error: "new" already defined

Поскольку я использую g ++, я надеялся, что использование их синтаксиса variadic macros даст успех, но, к сожалению, нет. Следующее:

#define new(...) new(__FILE__, __LINE__, ## __VA_ARGS__)

соответствует только new(xyx) A(), а не new A().

Я знаю, что были написаны эссе о том, почему это невозможно, но я чувствую, что я настолько близок, что должен быть способ. Есть ли что-то очевидное, чего мне не хватает?

Ответы [ 7 ]

23 голосов
/ 02 сентября 2009

Вот что я использую:

В new.cpp

const char* __file__ = "unknown";
size_t __line__ = 0;

void* operator new(size_t size) {
    void *ptr = malloc(size);
    record_alloc(ptr,__file__,__line__);
    __file__ = "unknown";
    __line__ = 0;
    return ptr;
}

void delete(void *ptr)
{
   unrecord_alloc(ptr);
   free(ptr);
}

Для компактности я опускаю другие определения новых и удаляю. «record_alloc» и «unrecord_alloc» - это функции, которые поддерживают связанный список структуры, содержащей ptr, строку и файл).

в новом .hpp

extern const char* __file__;
extern size_t __line__;
#define new (__file__=__FILE__,__line__=__LINE__) && 0 ? NULL : new

Для g ++ «new» раскрывается только один раз. Ключ "&& 0", который делает его ложным и вызывает использование реального нового. Например,

char *str = new char[100];

расширяется препроцессором до

char *str = (__file__="somefile.c",__line__=some_number) && 0 ? NULL : new char [100];

Таким образом записывается номер файла и строки, и вызывается ваша новая функция.

Это работает для любой формы new - до тех пор, пока в new.cpp есть соответствующая форма

8 голосов
/ 06 марта 2009

Вы должны проверить эту превосходную запись в блоге моего коллеги Кэлвина. Недавно у нас возникла ситуация, когда мы хотели включить исправления такого типа, чтобы связать утечки памяти со строкой, которая выделяла их в сборках диагностики / отладки. Это интересный трюк

http://blogs.msdn.com/calvin_hsia/archive/2009/01/19/9341632.aspx

4 голосов
/ 06 марта 2009

3.7.4 Динамическая продолжительность хранения

2 Библиотека предоставляет определения по умолчанию для глобальных функций выделения и освобождения. Некоторые функции глобального распределения и освобождения являются заменяемыми (18.5.1). Программа на C ++ должна содержать не более одного определения заменяемой функции выделения или освобождения. Любое такое определение функции заменяет версию по умолчанию, предоставленную в библиотеке (17.6.4.6) [...]

17.6.4.6 Функции замены

  1. Программа на C ++ может предоставить определение для любого из восьми динамических распределений памяти подписи функций, объявленные в заголовке (3.7.4, пункт 18):

    • оператор new (std :: size_t)
    • оператор new (std :: size_t, const std :: nothrow_t &)
    • оператор new [] (std :: size_t)
    • оператор new [] (std :: size_t, const std :: nothrow_t &)
    • оператор delete (void *)
    • оператор delete (void *, const std :: nothrow_t &)
    • оператор delete [] (void *)
    • оператор delete [] (void *, const std :: nothrow_t &)

Надеюсь, это проясняет, что является юридической перегрузкой, а что нет.

Это может заинтересовать некоторых здесь:

#define delete cout <<  "delete called at: " << __LINE__ << " of " << __FILE__  << endl, delete 

using namespace std;

void *operator new(size_t size, ostream& o, char *f, unsigned l) {
    o << "new called at: " << l << " of " << f << endl;
    return ::new char[size];
}

int main() {
    int *a = new(cout, __FILE__, __LINE__) int;
    delete a;
}

Caveat Lector : Что я здесь делаю, так это Плохая вещь (TM) - перегрузка new / delete глобально.

1 голос
/ 07 апреля 2009

Что вы можете сделать, это перегрузить оператор new и получить там трассировку стека (в зависимости от платформы) и использовать информацию стека для определения, откуда вызывался new.

1 голос
/ 10 марта 2009

Я обнаружил, что следующая библиотека « nvwa » очень полезна для отслеживания новых / удаления утечек памяти - посмотрите примеры файлов «debug_new» или просто используйте «как есть».

1 голос
/ 06 марта 2009

Вы не говорите, какой компилятор вы используете, но, по крайней мере, с GCC, вы можете переопределить новый и записать адрес вызывающего абонента, а затем преобразовать это в информацию о файле / строке с помощью addr2line (или использовать библиотеку BFD для этого немедленно).

0 голосов
/ 06 марта 2009

Нет, нет пути.

Вы могли бы сделать это в старые дурные времена malloc()/free(), но не для new.

Вы можете заменить распределитель памяти путем глобального переопределения оператора new, но вы не можете ввести специальные переменные, о которых вы говорите.

...