.NET Forum / Languages / Managed C++ / December 2006
New/delete vs. declare/garbage-collect
|
|
Thread rating:  |
Vincent Fatica - 15 Dec 2006 00:20 GMT I noticed that if I give MY_STRUCT a simple c'tor and d'tor, say,
MY_STRUCT::MY_STRUCT() { hEvent = CreateEvent(...); } MY_STRUCT::~MY_STRUCT { CloseHandle(hEvent); }
then using a local instance via simple declaration,
INT my_function() { MY_STRUCT foo; // blah return 0; }
adds nearly 6KB (most in the .text segment) to my target DLL when compared to using it via new/delete. Can someone explain why that happens? The example above is just a little simplified. The actual struct has as members a HANDLE, a BOOL, and an OVERLAPPED but still the only initialization I want is for the HANDLE. Thanks!
 Signature - Vince
Vincent Fatica - 15 Dec 2006 03:16 GMT Let me ask the same question another way (more to the point I hope). I use an ENUM_INFO (described below) struct like this:
INT function() { ENUM_INFO EnumInfo; //blah EnumWindows(..., &EnumInfo); // blah return 0; }
The struct and its c'tor and d'tor look like this:
struct ENUM_INFO { HANDLE hPipe; BOOL bGlobal; OVERLAPPED Overlapped; ENUM_INFO(); ~ENUM_INFO(); }; ENUM_INFO::ENUM_INFO() { Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); Overlapped.Offset = Overlapped.OffsetHigh = 0; }; ENUM_INFO::~ENUM_INFO() { //CloseHandle(Overlapped.hEvent); };
Quite simply, when I uncomment the CloseHandle() call (no other changes anywhere) in the d'tor, my target DLL increases in size by 6KB (most in the .text segment). There's nothing special about CloseHandle(); the same will be observed with any WIN32 API function in its place(e.g., Beep() or Sleep()). What's going on?
If I instantiate an ENUM_INFO using new/delete, I don't see the size increase.
Thanks.
 Signature - Vince
Carl Daniel [VC++ MVP] - 15 Dec 2006 05:12 GMT > Let me ask the same question another way (more to the point I hope). > I use an ENUM_INFO (described below) struct like this: [quoted text clipped - 36 lines] > If I instantiate an ENUM_INFO using new/delete, I don't see the size > increase. How many places in your code are you creating local variables of this type? Every single one of them will entail adding code to the function epilog to destroy the object that won't be there unless you explicitly delete it. Probably more importantly, having a destructor will cause any function containing the object to have an exception frame and exception handling code generated for it if you're compiling with -GX, -EHa or -EHs (in other words, if you have exception handling turned on - it's on by default in 2005). In exchange for that extra code size you code code will behave correctly in the face of exceptions.
-cd
Vincent Fatica - 15 Dec 2006 05:54 GMT >> Let me ask the same question another way (more to the point I hope). >> I use an ENUM_INFO (described below) struct like this: [quoted text clipped - 38 lines] > >How many places in your code are you creating local variables of this type? Only one place.
>Every single one of them will entail adding code to the function epilog to >destroy the object that won't be there unless you explicitly delete it. [quoted text clipped - 4 lines] >exchange for that extra code size you code code will behave correctly in the >face of exceptions. Well, it looks like compiler madness to me. If I make things a bit more complicated, like this:
ENUM_INFO::ENUM_INFO(BOOL boo, HANDLE hoo) { bGlobal = boo; hPipe = hoo; Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); Overlapped.Offset = Overlapped.OffsetHigh = 0; };
ENUM_INFO::~ENUM_INFO() { CloseHandle(Overlapped.hEvent); CloseHandle(hPipe); };
And use it as a local variable like this:
ENUM_INFO EnumInfo(stristr(psz, L"/G") ? TRUE : FALSE, CreateNamedPipe(szPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_WAIT, 1, 0, sizeof(EVENT), 2000, NULL));
My DLL drops the 6KB it gained from the set-up mentioned above.
 Signature - Vince
Vincent Fatica - 15 Dec 2006 06:09 GMT >>> Let me ask the same question another way (more to the point I hope). >>> I use an ENUM_INFO (described below) struct like this: [quoted text clipped - 74 lines] > >My DLL drops the 6KB it gained from the set-up mentioned above. Whereas (here's the real corker) if, in the code just above, I instantiate it thus:
ENUM_INFO EnumInfo(FALSE, NULL);
(regardless of whether I later set the real (desired) values of hPipe and bGlogal) the DLL size jumps back up 6KB. BTW, it's a release build.
 Signature - Vince
Arnaud Debaene - 15 Dec 2006 07:05 GMT >>ENUM_INFO::ENUM_INFO(BOOL boo, HANDLE hoo) >>{ [quoted text clipped - 26 lines] > (regardless of whether I later set the real (desired) values of hPipe and > bGlogal) the DLL size jumps back up 6KB. BTW, it's a release build. It's hard to be sure of anything without a complete sample (complete code + compiler settings + compiler version), but I would say that when you see the 6 Kb increase, it's because the compiler decides that you need exception safety mechanisms : that is, you've got a local (stack) variable with a non-trivial destructor. In this situation, in order to be exception-safe, the compiler needs to add an exception frame to the function where this variable is declared, and it needs to add all it's internal machinery that is used during stack-unwinding (eg, "catch" handlers tables, with filters by exception types....) The stack-unwinding machinery is quite complex and need a lot of static data (therefore the .text section increase).
However, I bet most of the 6 Ko increase is to be paid only once (that is, if you add a second function that also allocates an ENUM_INFO on the stack, you will see a much smaller increase).
Arnaud MVP - VC
Vincent Fatica - 15 Dec 2006 07:33 GMT >>>ENUM_INFO::ENUM_INFO(BOOL boo, HANDLE hoo) >>>{ [quoted text clipped - 37 lines] >exception types....) The stack-unwinding machinery is quite complex and need >a lot of static data (therefore the .text section increase). I don't know enough to say whether that's likely or not. But IMHO,
ENUM_INFO EnumInfo(FALSE, NULL);
(which causes the 6KB increase) looks safer than
ENUM_INFO EnumInfo(stristr(psz, L"/G") ? TRUE : FALSE, CreateNamedPipe(szPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_WAIT, 1, 0, sizeof(EVENT), 2000, NULL));
which doesn't.
 Signature - Vince
Vincent Fatica - 15 Dec 2006 16:58 GMT >>>>ENUM_INFO::ENUM_INFO(BOOL boo, HANDLE hoo) >>>>{ [quoted text clipped - 49 lines] > >which doesn't. FWIW, I'm building my DLL with VS2002/SP1. Ths settings are:
/O1 /Ob1 /Os /Oy /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_USRDLL" /D "EVENT2_EXPORTS" /D "_UNICODE" /D "UNICODE" /D "_WINDLL" /GF /FD /EHsc /ML /Gy /Zc:wchar_t /Fo"Release/" /Fd"Release/vc70.pdb" /W3 /nologo /c /Wp64 /Zi /TP
/OUT:"Release/ev.dll" /INCREMENTAL:NO /NOLOGO /DLL /DEF:"event.def" /SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /OPT:NOWIN98 /IMPLIB:"Release/event2.lib" /MACHINE:IX86 g:\Projects\event2\release\\takecmd.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib release\TakeCmd.lib
 Signature - Vince
Ben Voigt - 15 Dec 2006 17:24 GMT > On Fri, 15 Dec 2006 08:05:28 +0100, "Arnaud Debaene" > <adebaene@club-internet.fr> [quoted text clipped - 23 lines] > CreateNamedPipe(szPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, > PIPE_WAIT, 1, 0, sizeof(EVENT), 2000, NULL)); Not from the compiler's point of view, because in the second case, all the real work is done *before* calling the constructor. Any exceptions will cause the object not to be constructed, and therefore not need destruction.
This extra work for exception handling is a very good thing. With stack objects, you are guaranteed to (try to) free the kernel objects if an exception is thrown after the object is created. In fact, you should deploy the RAII idiom completely, so that every kernel object is in a separate variable that knows to free it. These can all be member variables of ENUM_INFO if you want. But, let's look at your code.
If the CreateEvent fails in the constructor, you may never CloseHandle on the pipe. You don't try to use the event handle in the constructor, so the object becomes live. Then, when you try to use the event handle (WaitForMultipleObjects perhaps) you could get an access violation. The compiler will begin stack unwinding, and call your destructor. The destructor call to CloseHandle the event fails with another access violation, immediately leaving the destructor. The pipe would not be closed.
Using individual RAII objects will cause them to be separately destructed, even if other destructors fail.
Getting all this right using new/delete would be: (1) a big pain (2) the __try/__finally logic would probably add the same 6kb as letting the compiler use unwinding.
If you are willing to accept process termination in case of any failure whatsoever, then disable exceptions. Otherwise, appreciate that the 6kb is necessary and generated by the compiler so you don't have to do it by hand.
> which doesn't. Vincent Fatica - 15 Dec 2006 18:00 GMT >But, let's look at your code. > [quoted text clipped - 6 lines] >violation, immediately leaving the destructor. The pipe would not be >closed. Thanks Ben. I wasn't worrying much about catching errors when trying to sort out the 6KB thing. I have this sort of thing now (paraphrased)
FOO::FOO() { handle1 = ... // NULL on fail } FOO:~FOO() { if (handle1) CloseHandle(handle1); }
code:
FOO foo; if ( !foo.handle1 ) ... // don't do anything rash else ... // life is right
 Signature - Vince
Vincent Fatica - 15 Dec 2006 17:46 GMT >It's hard to be sure of anything without a complete sample (complete code + >compiler settings + compiler version), but I would say that when you see the [quoted text clipped - 6 lines] >exception types....) The stack-unwinding machinery is quite complex and need >a lot of static data (therefore the .text section increase). Well, Arnaud, that seems very likely. When I remove /EHsc I can no longer cause the size increase. But I must repeat that whether or not the compiler decides to add that code is quite serendipitous. In the latest version, with the most the most going on in the c'tor and d'tor (below), the safety mechanisms are not added.
ENUM_INFO::ENUM_INFO(BOOL bg) { WCHAR szPipeName[32]; Sprintf(szPipeName, L"%s%lu", szPipeNameStub, g.dwPid); hPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED, PIPE_WAIT, 1, 0, sizeof(EVENT), 2000, NULL); Overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); Overlapped.Offset = Overlapped.OffsetHigh = 0; bGlobal = bg; };
ENUM_INFO::~ENUM_INFO() { if ( Overlapped.hEvent ) CloseHandle(Overlapped.hEvent); if ( hPipe ) CloseHandle( hPipe ); };
INT WINAPI function(WCHAR *psz) { // blah // instantiation ENUM_INFO EnumInfo(stristr(psz, L"/G") ? TRUE : FALSE); if ( !EnumInfo.hPipe || !EnumInfo.Overlapped.hEvent ) return -1; // do more? EnumWindows(..., &EnumInfo); // blah return 0; }
 Signature - Vince
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|