2009-12-13 4 views
1

Я не программировал C++ некоторое время и столкнулся с странным поведением при работе с перегруженными глобальными операторами new и delete. Суть проблемы заключается в том, что оболочка построена вокруг глобального new и находится в отдельном исходном файле , тем не менее, вызывает operator new, перегруженный в другом (и отдельно скомпилированном) исходном файле.Рекурсивные вызовы при попытке обернуть и переопределить глобальный оператор new

Почему это так, т.е. какие языковые правила/функции я нарушаю/неправильно использую?

Заранее благодарим за детали.

Структура проекта:

. 
.. 
main.cpp 
mem_wrappers.h 
mem_wrappers.cpp 

файлы проекта Содержание:

main.cpp

#include "./mem_wrappers.h" 
#include <iostream> 
#include <cstring> 


void* operator new[] (size_t sz) throw (std::bad_alloc) { 
    std::cout << "overloaded new()[]" << std::endl; 
    return default_arr_new_wrapper(sz); 
} 


int main() { 
    const unsigned num = 5; 
    int * i_arr = new int [num]; 

    return 0; 
} 

mem_wrappers.h

#include <cstring> 


void * default_arr_new_wrapper(size_t sz); 

mem_wrappers.cpp

#include <new> 
#include <cstring> 
#include <iostream> 


void * default_arr_new_wrapper(size_t sz) { 
    std::cout << "default_arr_new wrapper()" << std::endl; 
    return ::operator new[](sz); 
} 

Соблюден г ++ main.cpp mem_wrappers.cpp параметр --ansi --pedantic -Wall при запуске дает бесконечное operator new[] к default_arr_new_wrapper и наоборот вызовы приводит к следующему выходу :

overloaded new()[] 

default_arr_new_wrapper() 

overloaded new()[] 

default_arr_new_wrapper() 

... 

и, наконец, в SO (компилятор MS Visual Studio Express ведет себя одинаково).

Составители я использую: GCC версии 3.4.2 (MinGW-специальное) и MS Visual Studio 2008 Версия 9.0.30729.1 SP.

EDIT/UPDATE

Если (как первые ответы и комментарии предполагают) глобальный operator new эффективно переопределен для всего исполняемого файла просто перегружать его [оператор] в одном модуле компиляции, то:

это просто связывание с объектным файлом, источник которого перегружает глобальный operator new, заставляет ли какое-либо приложение или библиотеку изменять свою политику распределения памяти (ну, так называете ее)? Таким образом, этот оператор перегрузки эффективно обеспечивает крючок для языковой среды выполнения (я имею в виду то, что с этими символами компоновщика в уже скомпилированных без каких-либо перегруженных новых объектных файлах, заключается в том, что может быть только один new)?

P.S. Я знаю, что malloc и free сделали бы, и уже пробовали их перед публикацией здесь (работала нормально), но тем не менее, что стоит за этим поведением (и что, если бы я на самом деле обертывал по умолчанию operator new? :))?

+5

Как следует из названия, глобальный оператор новое носит глобальный характер. – 2009-12-13 16:43:24

+0

@Neil А как насчет связи (разве это не глобально для текущего блока компиляции)? – mlvljr

+1

mlvljr, вы можете поместить \ (обратная косая черта) перед вашими подчеркиваниями, чтобы избежать выделения курсивом следующих символов. Но в целом, когда вы ссылаетесь на имя переменной, предпочтительнее отображать ее как код, например 'varname'. –

ответ

8

Если прочитать формулировку в стандарте, что вы делаете для глобальной operator new функции, когда вы определить свой собственный называются заменой (не перегрузкой и не переопределяет). Большую часть времени, когда люди говорят об изменении глобальной функции operator new, они используют термин «перегрузка» и относятся к new как «оператор». Это часто приводит к путанице, когда результирующее поведение не согласуется с тем, что обычно ожидается от перегрузки оператора. По-видимому, это происходит в вашем случае. Вы ожидаете перегрузки поведение, но то, что вы действительно получаете, это замена: после того, как вы определили одну заменяющую версию глобальной функции operator new, она работает для всей программы. (И new это на самом деле не является оператором.)

В качестве примечания, замена является одним из тех редких «несправедливых» языковых особенностей, в том смысле, что она обычно не доступны пользователю. Вы не можете писать «сменные» функции в C++. Вы не можете написать что-нибудь, что будет вести себя так, как будут выполняться библиотеки по умолчанию для глобальных operator new и operator new. (Замена представляет собой проявление на уровне языка концепции компоновщика слабый символ).

+0

Отличный ответ, спасибо! – mlvljr

+0

Принимая это как очень большое добавление к комментарию Нила :)) – mlvljr

+0

, но почему gcc разрешило переопределить оператор new с той же сигнатурой в глобальном пространстве имен? VS не позволяет (компоновщик дает ошибку, указывающую, что имя уже определено). – Chubsdad

2

Не думаю, что после комментария Нила есть что добавить, но если вы перегружаете глобальный оператор new, вы не можете сделать это только для одной единицы перевода, new будет перегружен для всего исполняемого файла или dll.

Как вы уже заметили, вызывая глобальный оператор новым из вашего собственного перегруженного глобального оператора нового вы получите бесконечную рекурсию (ну, пока вы бежите из стека по крайней мере;)

Если вы хотите игрушку вокруг с этим попробуйте использовать malloc и free внутри вашей обертки, чтобы встать и работать.

Если операторы были локальны только для одной единицы перевода, вы столкнулись бы с проблемой, если бы вы передали объект, который был выделен одной версией new другой переводческой единице, и попытался удалить ее с помощью другого оператора, не соответствующего оператору delete.

Данные оператора new и delete приведены в разделе 18.4 стандарта C++.

+0

Итак, это глобально по-другому, чем, скажем, глобальные переменные, не так ли? Где я могу прочитать правила (и, вероятно, обоснование) для этого (т. Е. Стандарт, «Язык программирования на C++»)? – mlvljr

+0

P.S. Маллок и свободно работали хорошо, это странное поведение, в котором я ослеплен. – mlvljr

+0

И спасибо за обновление. – mlvljr

1

Чтобы развернуть комментарий Нейла, «глобальному» новому не может быть присвоен спецификатор класса хранения static и, следовательно, «глобальный» - он видна компоновщику вне модуля (может быть extern'd из любого другого модуль, например).

Invalid:

static void* operator new(size_t sz) {} 

Действительно:

//in main.cpp 
void* operator new(size_t sz) {} 

//in foo.cpp 
extern void* operator new(size_t sz); 

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

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

+0

Aha! Таким образом, разные единицы компиляции не могут полагаться на собственные операторы 'new'. – mlvljr

+0

Все, что объявлено в модуле, который не является статичным, видится компоновщику из-за пределов модуля. По сути, это цель «extern» - сообщить компилятору «этот var/func/etc объявлен в другом модуле». Хорошая практика - сделать все, что вы не ожидаете, ссылаться извне модуля (исходного файла) статично, но это не допускается в случае оператора new. –

+1

см. Http://msdn.microsoft.com/en-us/library/0603949d (VS.80).aspx и http://msdn.microsoft.com/en-us/library/s1sb61xd(VS.80). aspx для напоминания extern & static. –

1

Для того, чтобы усилить то, что AndreyT сказал:

Когда вы пишете собственную функцию глобального operator new вы дать определение, что компоновщик может видеть. Когда дело доходит до времени ссылки, компоновщику нужно будет найти определение для new, просто потому, что его использовали много. Он начинает поиск в объектных файлах, которые вы просите, чтобы они связывались, и hey-presto там он, поэтому он может остановить поиск.

Если вы не указали определение, оно провело бы поиск любых других объектных файлов, а затем через библиотеки, которые вы предоставляете, и, наконец, через время выполнения, которое поставляется вместе с вашим компилятором, который должен предоставить один или ничего не свяжет.

+0

+1, спасибо. [Пространственно-заполнения] – mlvljr

1

Глядя в стандартном Пбе GNU C++, вы видите украшения, как этот, то есть «слабое связывание»:

_GLIBCXX_WEAK_DEFINITION void * 
operator new (std::size_t sz) throw (std::bad_alloc)