2013-08-21 15 views
39

Ponieważ nasza aplikacja ma dużą wydajność i ograniczenia pamięci, nasze standardy kodowania zabraniają używania domyślnej sterty —, tj. Nr malloc, bez domyślnej wartości new. Każda alokacja pamięci musi wybrać jeden z kilku konkretnych alokatorów; cośCzy można całkowicie wyłączyć domyślnego operatora 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(); 

Chociaż mamy dobrą dyscyplinę prowadzonego w tej sprawie z naszym kodem, elementy Niektóre standardowe C++ (pojemniki, std::function) potajemnie wykonywać połączenia do domyślnego new sterty, który jest bardzo zły.

Byłoby miło całkowicie wyłączyć domyślną new w taki sposób, aby jakikolwiek wiersz kodu, który niejawnie powoduje domyślną alokację, natychmiast rzucił błąd kompilatora. To pozwoliłoby nam od razu zauważyć te rzeczy.

Możemy oczywiście dokonać new rzucać błąd wykonania tj

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

ale byłoby znacznie lepiej, aby ostrzeżenia o to w czasie kompilacji. Czy to jest możliwe?

Nasza aplikacja jest zbudowana na stałą platformę (x64 z Visual Studio); przenośność jest nieistotna.

+2

Czy próbowałeś patrząc na wyjściu łącznika? Sądzę, że na podstawie danych wyjściowych mapy linkera można by stwierdzić, czy wywoływane są procedury przydzielania. Nie wskazuje na krzywdzącą linię, ale przynajmniej byłaby w stanie przerwać w czasie budowy. –

+0

ciekawy wskaźnik tam: http://bytes.com/topic/c/answers/854450-there-any-way-disable-global-operator-new – SirDarius

+2

czy jest to opcja do implementacji domyślnej (wszystkie formy) nowej/usunąć pod względem niestandardowego nowego? Wydaje mi się, że można wyeksportować nowe/usunąć przy użyciu pliku .def, co powoduje (cały?) Proces przy użyciu eksportowanej wersji. – stijn

Odpowiedz

17

Możesz zaimplementować domyślną new, aby wywołać funkcję niezatwierdzoną. Następnie, w czasie łącza, dostaniesz błąd dla użytkowników gołej new rozmowy:

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

Kiedy testowałem go na IDEONE, mam ten błąd:

/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 

W moich testach, używając g++, nie ma błędu połączenia, jeśli nie ma odniesienia do gołego new w programie. Wynika to z faktu, że g++ nie emituje kodu dla nieużywanych funkcji inline.

Nie mam zainstalowanego Visual Studio w moim systemie, więc poniższe informacje oparte są tylko na niektórych dokumentach, które znalazłem. Aby operator widoczny był na całym obrazie, należy umieścić jego definicję w pliku nagłówkowym, a następnie użyć w kompilatorze opcji . * Zgodnie z this answer program Visual Studio nie wygeneruje kodu dla nieużywanych funkcji inline (takich jak g++). Należy jednak sprawdzić, czy istnieje poziom optymalizacji, który należy włączyć dla tego zachowania, czy nie.

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

Zakłada się, że zamierzasz przekazać własne podzielniki do szablonów C++ i klas w standardowej bibliotece C++. Jeśli nie, to inlined kod w standardowych nagłówków, które wymagają domyślnego przydzielania (który wezwie new) spowodują błąd łączące, jak również. Jeśli chcesz zezwolić standardowej bibliotece C++ na używanie domyślnej new, to prostym sposobem na jej działanie (kosztem dłuższego czasu kompilacji) jest dodanie wszystkich standardowych nagłówków C++, które zamierzasz umieścić na górze detect_bare_new.h plik.

Podajesz, że przenośność rozwiązania nie jest dla ciebie ważna.Ale dla kompletności, powinienem zwrócić uwagę na kwestię poprawnie wskazaną przez Ben Voigt: Standard C++ nie gwarantuje zachowania nie generowania kodu dla nieużywanych funkcji inline. Tak więc można uzyskać błąd połączenia, nawet jeśli funkcja nie jest używana. Ale jeśli kod nie ma innych odniesień do niezaimplementowanej funkcji, z wyjątkiem egzekucji wewnątrz kodu, to błąd ten mieściłby się w samej definicji new. Na przykład, g++ może generować błąd jak:

/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 

Jeśli system to taki, który generuje kod dla niewykorzystanych inline funkcji, można jeszcze obejścia. Obejście to zadziała, jeśli linker zgłosi wszystkie błędne odniesienia do niezdefiniowanej funkcji. W takim przypadku, jeśli jedyny zaobserwowany błąd powiązania wynika z definicji samego operatora new, nie ma nieoczekiwanych połączeń z nagim new. Po sprawdzeniu, że kod zawiera tylko jeden błąd, można zmienić linię łącza, aby uwzględnić obiekt lub bibliotekę, która ma odpowiednią definicję bare_new_erroneously_called(), która spowodowałaby wyjątek środowiska wykonawczego.

+0

Czysta sztuczka, muszę o tym pamiętać :-) – cmaster

+0

Czy jesteś pewien, że jest to zgodne z normą? [Odpowiedzi na to pytanie sugerują, że może nie być legalne] (http://stackoverflow.com/q/7297158/335858). – dasblinkenlight

+4

Ponieważ 'operator new' nie jest szablonem, spowoduje to błąd niezależnie od tego, czy zostanie wywołany czy nie. http://ideone.com/3KwAya –

1

Jeśli twój własny „nowy” operator nie nazywa się „nowy”, ale inaczej (na przykład „myNew”) można użyć „#define” w taki sposób, zastępując „nowy” przez śmieci:

#define new *+-/& 

pre-kompilator będzie teraz zastąpić "nowy":

x = new mytype; 

przez śmieci:

x = *+-/& mytype; 

wielką zaletę w porównaniu do MES sage w czasie łączenia jest to, że spowoduje to natychmiastowe wygenerowanie komunikatu kompilatora podczas kompilowania pliku C++, a nie na końcu podczas łączenia. Zobaczysz także linię, w której znajduje się "nowy".

Wadą jest to, że będziesz musiał "#include" plik zawierający ten "#define" we wszystkich plikach C++ w twoim projekcie.

+2

Nie wiem, czy to zadziała, gdy łączę się z bibliotekami wstępnie skompilowanymi. –

+0

@DanF: Zazwyczaj standardy kodowania nie obejmują bibliotek innych firm dostarczanych w postaci wstępnie skompilowanej. Nieinsertowana wersja mojej sugestii (i jej rozszerzenie obejmujące "malloc()" i znajomych) może wykryć, czy biblioteka używa alokacji dynamicznej, ale jedynym rozwiązaniem byłoby nadpisanie 'malloc()' i ' new() sam w sobie z innym zachowaniem lub rekompilował zmodyfikowane wersje bibliotek naruszających prawa. – jxh

+1

Ale to również przerwie 'new (g_SpecificAllocator) Thingy()', którego my * używamy *. – Crashworks

0

Poison it!
Jeśli używasz GCC, nie jest to dla Pragma:

#ifdef __GNUC__ 

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

#endif 
Powiązane problemy