What is the best memory management for GCC C++? [closed] - c++

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 months ago.
Improve this question
What's the problem of my memory management? Because it causes crash that I show in a comment in the code below ("A memory block could not be found when trying to free."). I know that my memory management is not thread safe because I use global variables g_numBlocks and g_blocks that can cause risk when using multiple threads.
Since my memory management code seems too complex, can anyone suggest a stable and better "Memory Management for C++" to avoid memory leaks.
The code that contains bug
#include "emc-memory.h" // <-- Declare the functions MALLOC() and FREE() from other library.
#include <vector>
int main() {
printf("HERE(1)\n");
{
std::vector<string> paths = { // <-- Problem, 'std::vector' & 'string' use internal malloc/free & operator new/delete that are overwritten with my own custom memory management.
"/foo/bar.txt",
"/foo/bar.",
"/foo/bar",
"/foo/bar.txt/bar.cc",
"/foo/bar.txt/bar.",
"/foo/bar.txt/bar",
"/foo/.",
"/foo/..",
"/foo/.hidden",
"/foo/..bar",
};
} // <-- It crashes here, error in FREE(): "A memory block could not be found when trying to free.".
printf("HERE(2)\n"); // The reason I know it crashes above is this line is not evaluated, only "HERE(1)" is printed. I'm using [RelWithDebInfo] with blurry debugging info.
return 0;
}
Compilers:
[Visual Studio 2015] [Debug]: No problem.
[Visual Studio 2015] [RelWithDebInfo]: No problem.
[GCC 12.1.0 x86_64-w64-mingw32] [Debug]: No problem.
[GCC 12.1.0 x86_64-w64-mingw32] [RelWithDebInfo]: Broken which means there's a bug.
In "emc-memory.h" in other library .so file
extern const char* __file;
extern int __line;
#define new (__file = __FILE__, __line = __LINE__, 0) ? 0 : new
enum MEMORYBLOCKTYPE {
MEMORYBLOCKTYPE_MALLOC,
MEMORYBLOCKTYPE_NEW,
};
void *MALLOC(size_t size, MEMORYBLOCKTYPE type);
void *REALLOC(void *block, size_t newSize);
void FREE(void *block, MEMORYBLOCKTYPE type);
#define malloc(size) ((__file = __FILE__, __line = __LINE__, 0) ? 0 : MALLOC(size, MEMORYBLOCKTYPE_MALLOC))
#define realloc(block, newSize) REALLOC(block, newSize)
#define free(block) FREE(block, MEMORYBLOCKTYPE_MALLOC)
In "emc-memory.cpp" in other library .so file
I use this code in a link to override the operator new & delete: https://codereview.stackexchange.com/questions/7216/custom-operator-new-and-operator-delete
typedef unsigned long long BlockId; // The reason it's 64-bit is a memory block can be freed and reallocated multiple times, which means that there can be a lot of ids.
BlockId g_blockId = 0;
BlockId newBlockId() {
return g_blockId++;
}
struct Block {
const char *file;
int line;
const char *scope;
char *hint;
size_t size;
BlockId id; // That id is used for comparison because it will never be changed but the block pointer can be changed.
void *block;
MEMORYBLOCKTYPE type;
};
bool g_blocks_initialized = false;
int g_numBlocks;
Block **g_blocks;
void *MALLOC(size_t size, MEMORYBLOCKTYPE type) {
if (g_blocks_initialized == false) {
g_blocks_initialized = true;
_initializeList(g_numBlocks, g_blocks);
}
Block *b = (Block *)malloc(sizeof(*b));
b->file = __file ; __file = nullptr;
b->line = __line ; __line = 0;
b->scope = __scope; __scope = nullptr;
b->hint = allocateMemoryHint(__hint);
b->size = size;
b->id = newBlockId();
b->block = malloc(size);
b->type = type;
_addListItem(g_numBlocks, g_blocks, b);
return b->block;
}
void FREE(void *block, MEMORYBLOCKTYPE type) {
if (block == nullptr) {
return; // 'free' can free a nullptr.
}
for (int i = 0; i < g_numBlocks; i++) {
Block *b = g_blocks[i];
if (b->block == block) {
if (b->type != type) {
switch (type) {
case MEMORYBLOCKTYPE_MALLOC: EMC_ERROR("The memory block type must be MALLOC."); break;
case MEMORYBLOCKTYPE_NEW: EMC_ERROR("The memory block type must be NEW."); break;
default: EMC_ERROR("Error"); break;
}
}
_removeListItem(g_numBlocks, g_blocks, b);
freeMemoryHint(b->hint); b->hint = nullptr;
SAFE_FREE(b->block);
SAFE_FREE(b);
return;
}
}
EMC_ERROR("A memory block could not be found when trying to free.\n\nExamples:\n - Calling free(pointer) where pointer was not set to zero after it's been called twice, the solution was to use SAFE_FREE(). And if possible, replace any free() with SAFE_FREE(). For example, see Lexer::read0() on the original line \"free(out.asIdentifier);\".\n - If an 'Engine' object is destroyed before destroying a Vulkan object then it can cause this error (It can happen with 'Release' or 'RelWithDebInfo' configuration but not with 'Debug' configuration), that problem happened to me and I stuck there for hours until I realized it.");
}

I would humbly suggest that without a very clear reason to think otherwise the best memory management for GCC C++ is the out-of-the-box default memory management for GCC C++.
That would mean your best solution would have been to do nothing or as it is now strip out your overrides of the global operators.
You may find in some area of a system the default memory management is sub-optimal but in 2022 the default options are very effective and if you find a general purpose strategy that is better it's a publishable paper.
However your question tells us nothing about the application in question or your motivations for thinking you should even try to change the memory management let alone give advice on what to.
Sure you can add a global allocation mutex to block memory management and make it thread-safe. I will be surprised if that doesn't turn out to more than throw away whatever advantage you're hoping to gain.

Not sure where SAFE_FREE is coming from.
If you see in MALLOC function, they use c runtime malloc. Meaning that if you want to free the block you need to use the corresponding free() function.
Make sure that SAFE_FREE is indeed using the c runtime free with the correct parameters.

Related

How to debug stack overflow in long if/else tree with many small static arrays [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 11 months ago.
Improve this question
I'm trying to compile and run a c++ project that someone else wrote. This works in MacOS and linux but fails with a stack overflow on Windows using MS Visual Studio 2022. The problem is in a huge function that has thousands upon thousands of cases in an if-else tree where the inner nestings have assorted multi-dimensional static arrays. For example, something like this, but 4500 lines long:
void foo(int mode) {
if (mode == 0) {
int vals[2][5];
...
}
else if (mode == 1) {
int vals[3][6][2];
...
}
...
}
How would typical compilers such as gcc and MS Visual Studio allocate stack space for this type of code pattern?
When compiling without optimisations enabled Visual Studio seems to allocate separate stack space for each of your variables (this isn't unreasonable, it makes debugging much easier if one variable doesn't suddenly change into another as you step though a function). When compiling in release mode it does optimise the unused arrays and allocate only the required stack space.
GCC does seem to not allocate separate stack space for each array even without optimisations enabled. Additionally the default stack size on macos and Linux is generally 8MB whereas on Windows it is usually 1MB.
The easiest solution to your problem is therefore probably to enable compiler optimisations. The better solution would be to refactor your code to be structured better.
For reference this is the code I used for testing:
#include <iostream>
void foo(int mode) {
if (mode == 1)
{
int foo[200'000];
std::cout << foo[mode];
}
else if (mode == 2)
{
int foo[200'000];
std::cout << foo[mode + 2];
}
else if (mode == 3)
{
int foo[200'000];
std::cout << foo[mode + 3];
}
}
int main()
{
int mode;
std::cin >> mode;
foo(mode);
}
In debug mode Visual Studio only allows 80,000 elements (80,000 * 4 (sizeof(int)) * 3 ~= 960kB).
In release mode it allows 200,000 elements (200,000 * 4 ~= 800kB).
GCC on Linux allows 2,000,000 elements (2,000,000 * 4 ~= 8MB).
Switch & Static
My quick suggestion is to use switch and static:
void Bar(const int mode)
{
switch (mode)
{
case 0:
{
static int vals[2][5];
//...
}
break;
}
}
Using the static keyword moves the allocation to another memory segment that usually has more capacity than the local (stack) area. A side effect is that all these array allocations will exist, whether the code goes through the case or not.
Table of Function Pointers
Another method is to use a table of <mode, function-pointer>. You could also use std::map.
typedef void (*Function_Pointer)();
void Do_Mode_0();
void Foo(const int mode)
{
static std::map<int, Function_Pointer> delegation_table;
static bool is_initialized = false;
if (!is_initialized)
{
is_initialized = true;
delegation_table[0] = Do_Mode_0;
//...
}
// Execute the function
Function_Pointer p_function = delegation_table[mode];
(*p_function)(); // Execute the function associated with the mode.
}
This will make your function (with all the cases) a lot smaller. You also have the luxury of placing each function in a separate file, and each file can have a different method to allocate the array.
Static & Const Table of Function Pointers
My preference is to use a static const table. Because the table is static and const, the table can be initialized at compile time.
struct Delegation_Entry
{
int key;
Function_Pointer p_function;
};
static const Delegation_Entry delegation_table[] =
{
{ 0, Do_Mode_0},
//...
};
static const table_size =
sizeof(delegation_table) / sizeof(delegation_table[0];
void Fuzz(const int mode)
{
for (int i = 0; i < table_size; ++i)
(
if (delegation_table[i].mode == mode)
{
Function_Pointer p_function = delegation_table[i].p_function;
(*p_function)();
break;
}
}
// Perform error handling if necessary
}
You can add more "cases" to the table without testing the code; since the code is data driven.

What is wrong with this void pointer to struct cast? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question does not appear to be about programming within the scope defined in the help center.
Closed 2 years ago.
Improve this question
The task here is to call out everything that is wrong with this snippet. I do not know if the code is being presented as C or C++, so either assumption is valid and helpful.
struct MyStructData
{
VOID* pPtr;
UINT32 flags;
}
VOID main()
{
MyStructData* pMyStructData = InitFunction();
// work done here
DeInitFunction(pMyStructData);
}
MyStructData* InitFunction()
{
MyStructData* pMyStructData = malloc(sizeof(MyStructData));
pMyStructData->pPtr = malloc(1024);
return pMyStructData;
}
VOID DeInitFunction(MyStructData* pMyStructData)
{
free(pMyStructData);
}
I see the void pointer is declared, then in InitFunction, pMyStructData has malloc with size of the struct MyStructData. Next the pPtr member is accessed and malloc-ed a big chunk (1024)... this is where I see an issue. Since the pPtr member was declared as void, pMyStructData is too small to allocate 1024 bytes?
Please help me to understand what is happening here, and what I missed.
Answer was provided before language tag was changed from C to C++.
struct MyStructData
{
VOID* pPtr;
UINT32 flags;
}
There are no types VOID or UINT32 in C unless you include some headers providing them. Maybe some Windows stuff?
Also a ; is missing after that struct declaration. (Thanks to wildplasser)
VOID main()
Valid signatures for main are int main(void) or int main (int argc, char *argv[]) or compatible.
{
MyStructData* pMyStructData = InitFunction();
There is no type MyStructData. Use struct MyStructData or add a typedef.
You do not provide a prototype for InitFunction().
// work done here
DeInitFunction(pMyStructData);
}
Same here: No prototype for DeInitFunction
MyStructData* InitFunction()
{
MyStructData* pMyStructData = malloc(sizeof(MyStructData));
Again: Use struct MyStructData.
Use of malloc without prototype. Include proper headers.
pMyStructData->pPtr = malloc(1024);
Basically valid (except for missing prototype), but you should check if pMyStructData is NULL before dereferencing it.
return pMyStructData;
}
VOID DeInitFunction(MyStructData* pMyStructData)
{
free(pMyStructData);
}
As Johnny Mopp mentioned, pMyStructData->pPtr is not freed causing a memory leak.
Again: No type VOID and MyStructData.
Again: Use of function (free) without prototype.
Now regarding your findings:
I see the void pointer is declared, then in InitFunction, pMyStructData has
malloc with size of the struct MyStructData.
Apart from the issues I mentioned abov, that is fine. That is how allocating memory for structs is don.
Next the pPtr member is accessed and malloc-ed a big chunk (1024)... this
is where I see an issue. Since the pPtr member was declared as void,
pMyStructData is too small to allocate 1024 bytes?
That is no issue. The member ptr points to some memory provided by malloc There is no relation to the struct where the pointer itself resides. It does not need to fit into the struct.
You only should check for NULL before this.

Dynamic vector allocation in external RAM

I am currently working on a large project of my own on an STM32F7 cortex-m7 microcontroller in C++ using GCC. I need to store a wide array in an external SDRAM (16 MB) containing vectors of notes structures (12 bytes each). I have already a working FMC and a custom ram region created
/* Specify the memory areas */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 512K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 1024K
SDRAM (xrw) : ORIGIN = 0xC0000000, LENGTH = 16M
}
/* Define output sections */
SECTIONS
{
/* Section créée pour l'allocation dans la SDRAM externe*/
.fmc :
{
. = ALIGN(8);
*(.fmc)
*(.fmc*)
. = ALIGN(8);
} >SDRAM
my array is declared like this :
std::vector<SequencerNoteEvent> NotesVectorArray[kMaxPpqn] __attribute__((section(".fmc")));
So far it's OK. I created an array of vectors in my external RAM. How can i proceed in order to make my vectors element creation
NotesVectorArray[position].push_back(note);
happening in the same external RAM dynamically ? I am currently only able to declare static data using the __attribute__(section)
I read lot of things about C++ allocators, memory pools, but i don't get where the allocation take place in the vector code and how i should replace it... I "just" need to have the same allocation system than the usual but in another part of my memory for this precise type.
It seems possible to have multiple heaps. Where is located the connection between scatter file and the effective memory allocation ?
Thanks in advance,
Ben
I was facing the same issue and got a working solution by redefining operator new. The benefit of this over using a custom allocator is that all dynamic allocations will automatically be in SDRAM, so you're free to use std::function or any STL container that requires the heap, without having to pass a custom allocator argument every time.
I did the following:
Remove the standard library by adding -nostdlib to the compilation flags. I also added this to the linker flags. Also remove and --specs=... from your compiler and linker flags. Bonus: you'll save ~60-80k of code space!
Write your own replacement for operator new, operator delete. I created a new file called new.cpp added the following: (taken from https://arobenko.gitbooks.io/bare_metal_cpp/content/compiler_output/dyn_mem.html)
//new.cpp
#include <cstdlib>
#include <new>
void *operator new(size_t size) noexcept { return malloc(size); }
void operator delete(void *p) noexcept { free(p); }
void *operator new[](size_t size) noexcept { return operator new(size); }
void operator delete[](void *p) noexcept { operator delete(p); }
void *operator new(size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete(void *p, std::nothrow_t) noexcept { operator delete(p); }
void *operator new[](size_t size, std::nothrow_t) noexcept { return operator new(size); }
void operator delete[](void *p, std::nothrow_t) noexcept { operator delete(p); }
Define a variable in the linker script that corresponds to the lowest SDRAM address (start of the heap). This isn't strictly necessary, as you could use a constant (0xC0000000) in your code in step 4, but it makes things easier to keep the SDRAM address in just one place. Just add one line: PROVIDE( _fmc_start = . );
//linker script
[snip]
/* Section créée pour l'allocation dans la SDRAM externe*/
.fmc :
{
. = ALIGN(8);
PROVIDE( _fmc_start = . );
*(.fmc)
*(.fmc*)
. = ALIGN(8);
} >SDRAM
The new implementation of new will directly call malloc(), which will call _sbrk() when it needs a block of memory in the heap.
So you have to define _sbrk() to keep track of the heap pointer, and simply start the pointer at the lowest SDRAM address that you just defined in the linker script.
Here's what I did:
//sbrk.cpp, or append this to new.cpp
#include <cstdlib>
extern "C" size_t _sbrk(int incr)
{
extern char _fmc_start; // Defined by the linker
static char *heap_end;
char *prev_heap_end;
if (heap_end == 0)
heap_end = &_fmc_start;
prev_heap_end = heap_end;
//Optionally can check for out-of-memory error here
heap_end += incr;
return (size_t)prev_heap_end;
}
At this point, if you try to compile you will probably get many linker errors. The exact errors will depend on what parts of the standard library your project uses besides just new and delete. In my case, I was using std::function and the compiler complained that it didn't have __throw_bad_function_call(). You probably will also see an error for things like _init() and _fini() and __errno. The basic strategy is to create your own empty function stub or variable declaration for each of these. It's worth doing a quick search for each of these to see what purpose they serve, you might find out your project or a library you're including is using some features you weren't aware of! When you create a stub, you'll need to match the function signature correctly, so that will require searching the internet as well. Many of the stubs are for handling errors/exceptions, so you can decide how to handle it in the function body (or ignore errors).
For example, here's some info on the (obsolete, but required) _init() and _fini() functions: https://tldp.org/HOWTO/Program-Library-HOWTO/miscellaneous.html
Here's some info on __dso_handle: https://lists.debian.org/debian-gcc/2003/07/msg00057.html
__cxa_pure_virtual(): What is the purpose of __cxa_pure_virtual?
Here's what my project required to work:
//syscalls.cpp
extern "C" {
void _init(void) {}
void _fini(void) {}
int __errno;
void *__dso_handle = (void *)&__dso_handle;
}
namespace std
{
//These are error handlers. They could alternatively restart, or log an error
void __throw_bad_function_call() { while (1); }
void __cxa_pure_virtual() { while (1); }
} // namespace std
After all that, it will finally compile and if you use a debugger you'll see that the address of your vectors start at 0xC0000000!

Extend call stack to disk in C++?

When it comes to massively-recursive method calls, the call-stack size has to be extended by modifying the appropriate compiler parameters in order to avoid stack-overflow.
Let's consider writing a portable application whose layout is simple enough so that its users need only possess minimal technical knowledge, so manual virtual memory configuration is out of question.
Running massively-recursive methods (behind the scenes obviously) may result in the call-stack limit being surpassed, especially if the machine the application is running on is limited memory-wise.
Enough chit-chat: In C++ is it possible to manually extend the call-stack to disk in case memory is (almost) full?
It may just barely be possible.
Use a coroutine library. With that, you allocate your own stack out of the heap. Restructure your code to track how deep it is in its callstack, and when it gets dangerously deep, create a new cothread and switch into that instead. When you run out of heap memory, freeze old cothreads and free their memory. Of course, you better be sure to unfreeze them to the same address--so I suggest you allocate their stacks yourselves out of your own arena that you can control. In fact it may be easier to just reuse the same piece of memory for the cothread stack and swap them in and out one at a time.
It's certainly easier to rewrite your algorithm to be non-recursive.
This may be an example of it working, or it may just print the right answer on accident:
#include <stdio.h>
#include "libco.h"
//byuu's libco has been modified to use a provided stack; it's a simple mod, but needs to be done per platform
//x86.c:
////if(handle = (cothread_t)malloc(size)) {
//handle = (cothread_t)stack;
//here we're going to have a stack on disk and have one recursion's stack in RAM at a time
//I think it may be impossible to do this without a main thread controlling the coroutines, but I'm not sure.
#define STACKSIZE (32*1024)
char stack[STACKSIZE];
FILE* fpInfiniteStack;
cothread_t co_mothership;
#define RECURSING 0
#define EXITING 1
int disposition;
volatile int recurse_level;
int call_in_cothread( int (*entrypoint)(int), int arg);
int fibo_b(int n);
int fibo(int n)
{
if(n==0)
return 0;
else if(n==1)
return 1;
else {
int a = call_in_cothread(fibo,n-1);
int b = call_in_cothread(fibo_b,n-2);
return a+b;
}
}
int fibo_b(int n) { printf("fibo_b\n"); return fibo(n); } //just to make sure we can call more than one function
long filesize;
void freeze()
{
fwrite(stack,1,STACKSIZE,fpInfiniteStack);
fflush(fpInfiniteStack);
filesize += STACKSIZE;
}
void unfreeze()
{
fseek(fpInfiniteStack,filesize-STACKSIZE,SEEK_SET);
int read = fread(stack,1,STACKSIZE,fpInfiniteStack);
filesize -= STACKSIZE;
fseek(fpInfiniteStack,filesize,SEEK_SET);
}
struct
{
int (*proc)(int);
int arg;
} thunk, todo;
void cothunk()
{
thunk.arg = thunk.proc(thunk.arg);
disposition = EXITING;
co_switch(co_mothership);
}
int call_in_cothread(int (*proc)(int), int arg)
{
if(co_active() != co_mothership)
{
todo.proc = proc;
todo.arg = arg;
disposition = RECURSING;
co_switch(co_mothership);
//we land here after unfreezing. the work we wanted to do has already been done.
return thunk.arg;
}
NEXT_RECURSE:
thunk.proc = proc;
thunk.arg = arg;
cothread_t co = co_create(stack,STACKSIZE,cothunk);
recurse_level++;
NEXT_EXIT:
co_switch(co);
if(disposition == RECURSING)
{
freeze();
proc = todo.proc;
arg = todo.arg;
goto NEXT_RECURSE;
}
else
{
recurse_level--;
unfreeze();
if(recurse_level==0)
return thunk.arg; //return from initial level of recurstion
goto NEXT_EXIT;
}
return -666; //this should not be possible
}
int main(int argc, char**argv)
{
fpInfiniteStack = fopen("infinite.stack","w+b");
co_mothership = co_active();
printf("result: %d\n",call_in_cothread(fibo,10));
}
Now you just need to detect how much memory's in the system, how much of it is available, how big the callstack is, and when the callstack's exhausted, so you know when to deploy the infinite stack. That's not easy stuff for one system, let alone doing it portably. It might be better to learn how the stack is actually meant to be used instead of fighting it.
It's feasible.
You need a bit of assembly to manipulate the stack pointer as there's no standardized way of accessing it from C++ directly (as far as I know). Once you are there you can point to your memory page and take care of swapping memory in and out. There are already libraries out there doing it for you.
On the other hand if the system provider considered that paging memory or the other virtual memory techniques would not work/be worth on the platform they probably had a very good reason (most likely it would be incredibly slow). Try to get your solution to work without the recursion or change it to make the recursion fit into what's available. Even a less efficient implementation would end up faster than your disk paged recursion.

Identify if object is allocated in static memory block (or how to avoid data race conditions)

Preface:
this question is closely related to these ones: ...
- C++: Avoiding Static Initialization Order Problems and Race Conditions Simultaneously
- How to detect where a block of memory was allocated?
... but they have NO positive solution and my actual target use-case is slightly different.
During construction of the object I need to know if it is initialized in static memory bock ( BSS) or is it instantiated in Heap.
The reasons are follow:
Object by itself is designed to be initialized to "all zeros" in constructor - therefore no initialization is needed if object is statically initialized - entire block with all objects is already set to zeros when program is loaded.
Static instances of the object can be used by other statically allocated objects and alter some member variables of the object
Order of initialization of static variables is not pre-determined - i.e. my target object can be invoked before its constructor is invoked, thus altering some of its data, and constructor can be invoked later according to some unknown order of initialization of statics thus clearing already altered data. That is why I'd like to disable code in constructor for statically allocated objects.
Note: in some scenarios Object is the subject for severe multi-threaded access (it has some InterlockedIncrement/Decrement logic), and it has to be completely initialized before any thread can touch it - what i can guaranteed if i explicitly allocate it in Heep, but not in static area (but i need it for static objects too).
Sample piece of code to illustrate the case:
struct MyObject
{
long counter;
MyObject() {
if( !isStaticallyAllocated() ) {
counter = 0;
}
}
void startSomething() { InterlockedIncrement(&counter); }
void endSomething() { InterlockedDecrement(&counter); }
};
At the moment I'm trying to check if 'this' pointer in some predefined range, but this does not work reliably.
LONG_PTR STATIC_START = 0x00400000;
LONG_PTR STATIC_END = 0x02000000;
bool isStatic = (((LONG_PTR)this >= STATIC_START) && (LONG_PTR)this < STATIC_END));
Update:
sample use-case where explicit new operator is not applicable. Code is 'pseudo code', just to illustrate the use-case.
struct SyncObject() {
long counter;
SyncObject() {
if( !isStaticallyAllocated() ) {
counter = 0;
} }
void enter() { while( counter > 0 ) sleep(); counter++; }
void leave() { counter--; }
}
template <class TEnum>
struct ConstWrapper {
SyncObject syncObj;
TEnum m_value;
operator TEnum() const { return m_value; }
LPCTSTR getName() {
syncObj.enter();
if( !initialized ) {
loadNames();
intialized = true;
}
syncObj.leave();
return names[m_value];
}
}
ConstWrapper<MyEnum> MyEnumValue1(MyEnum::Value1);
You can probably achieve this by overwriting the new operator for your class. In your customized new, you can set a "magic byte" within the allocated memory, which you can later check for. This will not permit distinguishing stack from heap, but statically from dynamically allocated objects, which might be sufficient. Note, however, that in the following case
class A {
};
class B {
A a;
};
//...
B* b = new B;
b.a will be considered statically allocated with the proposed method.
Edit: A cleaner, but more complicated solution is probably a further customization of new, where you can keep track of dynamically allocated memory blocks.
Second edit: If you just want to forbid static allocation, why don't you just make the constructor private and add a factory function to the class dynamically creating the object and delivering the pointer?
class A {
private:
A () { ... }
public:
static A* Create () { return new A; }
};
I think that the best way for you to control this is to create a factory for your class. That way you have complete control of how your objects are created instead of making complicated guesses over what memory is used.
The first answer is: not portably, and it may not be possible at all on
some platforms. Under Solaris (and I think Linux as well), there is an
implicitly defined global symbol end, comparison of arbitrary
addresses works, and if this < &end (after the appropriate
conversions), the variable is static, at least as long as no dynamic
loading is involved. But this is far from general. (And it definitely
fails anytime dynamic linking is involved, regardless of the platform.)
The solution I've used in the past was to make the distinction manually.
Basically, I designed the class so that the normal constructor did the
same thing as zero initialization, and I then provided a special no-op
constructor for use with static objects:
class MayBeStatic
{
public:
enum ForStatic { isStatic };
MayBeStatic() { /* equivalent of zero initialization */ };
MayBeStatic( ForStatic ) { /* do absolutely nothing! */ };
// ...
};
When defining an instance with static lifetime, you use the second
constructor:
MayBeStatic object( MayBeStatic::isStatic );
I don't think that this is guaranteed by the standard; I think the
implementation is allowed to modify the memory any way it wants before
invoking the constructor, and in particular, I think it is allowed to
"redo" the zero initialization immediately before invoking the
constructor. None do, however, so you're probably safe in practice.
Alternatively, you can wrap all static instances in a function, so that
they are local statics, and will be initialized the first time the
function is called:
MayBeStatic&
getStaticInstance()
{
static MayBeStatic theInstance;
return theInstance;
}
Of course, you'll need a separate function for each static instance.
It looks like after thinking for a while, I've found a workable solution to identify if block is in static area or not. Let me know, please, if there are potential pitfalls.
Designed for MS Windows, which is my target platform - by another OS I actually meant another version of MS Windows: XP -> Win7. The idea is to get address space of the loaded module (.exe or .dll) and check if block is within this address space. Code which calculates start/end of static area is put into 'lib' segment thus it should be executed before all other static objects from 'user' segment, i.e. constructor can assume that staticStart/End variables are already initialized.
#include <psapi.h>
#pragma warning(push)
#pragma warning(disable: 4073)
#pragma init_seg(compiler)
#pragma warning(pop)
HANDLE gDllHandle = (HANDLE)-1;
LONG_PTR staticStart = 0;
LONG_PTR staticEnd = 0;
struct StaticAreaLocator {
StaticAreaLocator() {
if( gDllHandle == (HANDLE)-1 )
gDllHandle = GetModuleHandle(NULL);
MODULEINFO mi;
GetModuleInformation(GetCurrentProcess(), (HMODULE)gDllHandle, &mi, sizeof(mi));
staticStart = (LONG_PTR)mi.lpBaseOfDll;
staticEnd = (LONG_PTR)mi.lpBaseOfDll + mi.SizeOfImage;
// ASSERT will fail in DLL code if gDllHandle not initialized properly
LONG_PTR current_address;
#if _WIN64
ASSERT(FALSE) // to be adopted later
#else
__asm {
call _here
_here: pop eax ; eax now holds the [EIP]
mov [current_address], eax
}
#endif
ASSERT((staticStart <= current_address) && (current_address < staticEnd));
atexit(cleanup);
}
static void cleanup();
};
StaticAreaLocator* staticAreaLocator = new StaticAreaLocator();
void StaticAreaLocator::cleanup() {
delete staticAreaLocator;
staticAreaLocator = NULL;
}