[c++]kompatybilnosc z C,czyli jak wylaczyc niechciane (N)RVO

0

edit-quetz-oryginalny temat: 'return by value + rvo? = wtf'

po 3 dniach kopania, i z racji godziny, jestem z lekka wyprawny umyslowo, wiec wybaczcie temat, poprawie go jak wpadne na cos bardziej sensownego..

target: msvc 9.0 SP1 (2009), x86, 32bit

problem brzmi tak:
napisac odpowiednik funkcji z C, jako metode C++, zachowujac 'binary compatibility' wstecz do C
dokladniej:
istniala kiedys funkcja, napisana w C, w konwencji cdecl, ktora pobierala parametry typu 'typedef unsigned long VALUE', i zwracala jedna takąż wartosc
po stronie bebechow - argumenty przez stos, odkladane od prawej do lewej, wszystkie bezposrednio przez wartosc, wartosc zwracana przez EAX, bezposrednio przez wartosc

stoje przez zadaniem napisania jej odpowiednika w C++, jako metody niestatycznej pewnego obiektu klasy niewirtualnej, dziedziczacej pojedynczo. i nie byloby w tym wielkiego problemu, gdyby nie fakt, ze VALUE musi ulec zmianie na ... "cos co ma metody", czyli strukture/klase/unie, nazwijmy to "Value" dla odmiany. oczywiste jest, ze wypadkowy sizeof(Value)==sizeof(VALUE), np:

orig:

typedef unsigned long VALUE;
VALUE __cdecl my_module_function(VALUE mymoduledata, VALUE param1, VALUE param2)
{
    return param2; //ot, cos tam
}

nowy-prawie:

struct Value
{
    unsigned long val;
    // WSZYSYKIE METODY USUNIETE, na czas problemu jest to POD i POD'em bedzie docelowo rowniez
};

struct my_module{
    Value __cdecl function(Value param1, Value param2);
};

Value __cdecl my_module::function(Value param1, Value param2)
{
    return param2; //ot, cos tam
}

Value p1,p2,result;
my_module obj_of_my_module;
result = obj_of_my_module.function( p1, p2 );

na MSVC9.0, wszystkie metody obiektow sa odmyslnie budowane jako __thiscall, czyli this poprzez ECX a reszta jak w stdcall. wymuszenie cdecl powoduje, ze this jest przekazywany jako ukryty parametr na stosie, odkladany na samym koncu (czyli jako pierwszy parametr funkcji, reszta zostaje zsunieta o 1 w dol) - to gwarantuje ze sposob przekazania oraz kolejnosc parametrow jest identyczny z oryginalem.
klasa 'Value' zostala z premedytacja ograbiona ze wszytkeigo, jest chamskim POD'em, param1 i param2 sa przekazywane przez wartosc, defacto przekazanie param1 i param2 jest realizowane przez odlozenie na stosie wprost ich wartosci value - jupiii - zupelnie jak w originale. ale zycie takie piekne nie jest..

return value jest tez typu Value, miesci sie w rejestrze, wiec spodziewac by sie mozna ze zostanie odebrane tak jak w C - czyli przez wcisniecie jego zawartosi do EAX:
mov edx, [p2]
push edx
mov ecx, [p1]
push eax
mov ecx, [obj_of_my_module]
push ecx, //'this'
call ....
add esp, 0xC // cdecl - caller czysci stos
mov [result], eax // zwrocona wartosc

a tu, merde! wcale tak nie jest! kod wygenerowany przez msvc w przyblizeniu:
mov edx, [p2]
push edx
mov ecx, [p1]
push ecx
lea eax, [result] // ptr na wynik
push eax
mov ecx, [obj_of_my_module]
push ecx, //'this'
call ....
add esp, 0xC
//--i nic wiecej

MIMO ze struktura jest POD'em i ze MIESCI sie w 32bit, msvc postanowil przekazac wynik poprzez ukryty wskaznik. no żesz.. pierwsza moja mysl byla - wcielo sie jakies (N)RVO - ale nie, po przemysleniu sprawy, wydaje mi sie ze jednak nie, gdyz obserwujac zachowanie sie innych metod, to podejscie wydaje sie byc dla kompilatora standardowe.. przekopalem sie przez msdn i tone materialow na sieci, jedyna sensowna wzmianka na jaka trafilem to to, ze "msvc6.0 poszerza wartosci do 32bit i zwraca przez EAX, 64-bitowe przez EDX:EAX, wieksze zas - przez ukryty wskaznik". super. ale moja struct jest 32bit!

zirytowalem sie, obcialem metode ze wszystkich parametrow i zaczalem testowanie - myslalem ze moze jakies extern"C"/register/const/volatile/__declspec/etc na parametrach czy na this'ie pozwoli wymusic potraktowanie jej tak jak w C - gdzież tam, po prostu nie idzie cholery zmusic do zwrocenia tej jednopolowej struktury zywcem poprzez EAX'a. moze jakas #pragma ktorej nie znalazlem??

a żeby było ciekawiej:

struct Value
{
    unsigned long val;
};

Value __cdecl function(Value param1, Value param2);  // non-member!!

Value __cdecl my_module__function(Value param1, Value param2) // non-member!!
{
    return param2; //ot, cos tam
}

Value this,p1,p2,result;
result = my_module__function( this, p1, p2 );

powyzszy kod, kompilowany jako, podkreslam, rowniez C++, nie jako C, powoduje ze ..... return value zostaje na zywca spakowane do EAX'a, bez hidden pointera !!!

ok. moznaby stwierdzic, ze moooze, jak cos wyglada jak funkcja ze starego C, to zasady sa troche inne, a jak siedzi w klasie - czyli wyraznie C++ - to kompilator sobie folguje innym traktowaniem return values..

ale ponizszy kod jest w 100% tak samo wywolywany:

struct Value
{
    unsigned long val;
};

class my_module{
    static Value __cdecl function(my_module* athis, Value param1, Value param2); // static member!!
};

Value __cdecl my_module::function(my_module* athis, Value param1, Value param2) // static member!!
{
    return param2; //ot, cos tam
}

Value p1,p2,result;
my_module obj_of_my_module;
result = my_module__function( &obj_of_my_module, p1, p2 );

wynik:
0052272B mov edx,dword ptr [p2]
0052272E push edx
0052272F mov eax,dword ptr [p1]
00522732 push eax
00522733 mov ecx,dword ptr [obj_of_my_module]
00522736 push ecx
00522737 call my_module::function(464207h)
0052273C add esp,Ch
0052273F mov dword ptr [ebp-54h],eax //smieciowe przepisanie przez temp
00522742 mov edx,dword ptr [ebp-54h]
00522745 mov dword ptr [result],edx

i gdzie tutaj sens? :/
jesli funkcja jest:
-- free function albo static member: zwraca Value jako POD upakowany do rejestru EAX
-- nonstatic member: zwraca Value poprzez ukryty parametr-wskaznik na docelowe zmienna z caller'a

i to pieknie rozwala kompatybilnosc.

czy mozna jakos zmusic kompilator MSVC9.0 ew 10.0 (2010) aby łaskawie zechcial upakowac do EAX to co najwyrazniej potrafi tam upadkowac (patrz przypadek metody statycznej w C++) - tylko jakos czasem mu sie nie chce?


a teraz slowem wyjanienia:
mam zrdla programu, dzialajacego, napisanego w C. jest to w istocie mniej lub bardziej luzny zlepek kilku modulow, przyczym moduly te zachowuja sie szalenie obiektowo -- kazdy z nich dostarcza swoich funkcji, oraz dostarcza kilku/nastu VALUE na ktorych mozna wykonac funkcje z tego modulu (tzn.: mozna wywolac funkcje z tego modulu, podajac im te tutaj wartosci VALUE), ale NIE mozna na wywolywac funkcji z innych modulow podjac im te VALUE'sy - tamte moduly ich nie zrozumieja.
chce dopisac do tego programu swoj modul, mam juz szczerze dość okazjonalnego mylenia VALUE z modułu 'lewego' z VALUE z modulu 'prawego', a jeszcze badziej mam dosc uwazania na to, a jeszcze bardiej mnie wkurza skladnia modul_xyz_zrobTo(obiekt, param, pram) i chce po prostu napisac ... klase Value bedaca proxy/wrapperem na te cholerne VALUE.
jak widac nie ma problemu z otrzymywaniem i zwracaniem VALUE widzianego u celu jako Value, problem pojawil sie ze zwracaniem Value z tego "niszowego" przypadku jakim jest zwykla metoda zwyklego obiektu majaca zwrocic POD'a..

0

po paru kolejnych testach, zaczynam sadzic ze RVO jest traktowane obowiazkowo..
//edit: w standardzie 98, 03 i draft 08 ani slowa.. eh

1 użytkowników online, w tym zalogowanych: 0, gości: 1