2010-09-29 10 views
8

Mam projekt składający się z pęczek dynamicznie ładowanych modułów. Początkowo wszystko było zawsze budowane z MSVC 2003, ale ostatnio pracuję nad tym, żeby to działało z GCC. Wszystko szło całkiem sprawnie, z wyjątkiem jednego problemu. W przypadku kodu 64-bitowego GCC i MSVC nie zgadzają się co do tego, czym jest va_list. W przypadku wersji 32-bitowych wszystko wygląda dobrze. Problem związany z niedopasowaniem 64-bitowym ma miejsce, gdy moduł zbudowany z jednym kompilatorem ma funkcję publiczną z parametrem va_list i ta funkcja jest wywoływana z modułu zbudowanego przez inny kompilator.Dopasowywanie typów va_list między kompilatorami

Spec mówi nic o tym, co va_list jest poza Rozdział 7.15 zmiennych argumentów <stdarg.h> ustęp 3:

typ zgłoszony jest

va_list

co jest typ obiektu odpowiedni do przechowywania informacji wymaganych przez makra va_start, va_arg, va_end i va_copy.

To właśnie oznacza paragraf to wszystko zależny rzeczy kompilator - tak, czy istnieje sposób, aby te dwa kompilatory uzgodnić zawartości 64-bit va_list? Najmniejszy wpływ na mój system, sprawiając, że GCC pasuje do MSVC va_list, byłoby najlepsze, ale podejmiemy każde możliwe rozwiązanie.

Dzięki za pomoc!

Edit:

Zrobiłem kilka testów 32-bitowego, a ja mam problemy tam też, która mnie zaskoczyła, ponieważ istnieją podobno ma różnic pomiędzy dowolnymi ABI 32-bitowych platform Intela. Kodzie MSVC używam definiuje wszystkie o zmiennej liczbie argumentów makr funkcyjnych jak:

typedef char *va_list; 
#define intsizeof(n) ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1)) 
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v)) 
#define va_arg(ap, t) (*(t *) ((ap += intsizeof(t)) - intsizeof(t))) 
#define va_end(ap)  (ap = (va_list)0) 

uprościłem nieco od realnego projektu, ale jest to kod używałem dla mojego testu. W GCC ten kod zdecydowanie nie poprawnie pobiera moje argumenty. Może to tylko błąd, jak sugeruje Zack poniżej?

Edycja ponownie:

dostaję wyniki pracy na kolejny 32-bitowych aplikacji testowej z -O0, -O0 i -O2, lecz nie -O3, -Os i -Oz:

typedef char *va_list; 
#define intsizeof(n) ((sizeof(n) + sizeof(int) - 1) &~(sizeof(int) - 1)) 
#define va_start(ap, v) (ap = (va_list)&(v) + intsizeof(v)) 
#define va_arg(ap, t) (*(t *) ((ap += intsizeof(t)) - intsizeof(t))) 
#define va_end(ap)  (ap = (va_list)0) 

int printf(const char *format, ...); 

int f(int n, ...) 
{ 
    int r = 0; 
    va_list ap; 

    va_start(ap, n); 
    while (n--) 
    r = va_arg(ap, int); 
    va_end(ap); 

    return r; 
} 

int main(int argc, char **argv) 
{ 
    int r; 

    r = f(1, 1, 2, 3, 4, 5); 
    printf("%x\n", r); 

    r = f(2, 1, 2, 3, 4, 5); 
    printf("%x\n", r); 

    r = f(3, 1, 2, 3, 4, 5); 
    printf("%x\n", r); 

    r = f(4, 1, 2, 3, 4, 5); 
    printf("%x\n", r); 

    r = f(5, 1, 2, 3, 4, 5); 
    printf("%x\n", r); 

    return 0; 
} 
+0

Uh. Skopiowałeś definicje 'va_ *' z '' z pliku MSVC do pliku, który następnie skompilowałeś z GCC, czy tak jest? Ponieważ to na pewno nie zadziała i nie powie nic pożytecznym. Musisz bezwzględnie użyć GCC '' aby zdefiniować funkcje variadyczne skompilowane przy pomocy GCC (i MSVC dla MSVC) lub twój kod * zostanie * skompilowany. – zwol

+0

To, co musisz zrobić dla tego testu, to przenieść 'f' do własnego pliku, zamienić wszystkie definicje rąk' va_ * 'na' #include ', wstaw" 'extern int f (int n, ...) ; '' above 'main' w tym pliku, skompiluj jeden z GCC, a drugi z MSVC i połącz oba pliki obiektów. * To * powinno działać w obu kierunkach (wywołania MSVC GCC lub GCC wywołuje MSVC) na x32 lub x64. – zwol

+0

Tak, to działało dobrze podczas wyłączania wprowadzania lub umieszczania go w oddzielnej jednostce kompilacji.Tak w każdym razie jest to całkowicie oddzielny problem od niedopasowanych typów va_list, co jak mówisz, jest błędem z kompilatorem –

Odpowiedz

5

Ponieważ MSVC definiuje ABI Win64, znalazłeś błąd w GCC. Proszę zgłosić to w GCC bugzilla.

+0

'' va_list' nie jest częścią ABI? W każdym razie moim długoterminowym planem jest użycie clang/llvm, więc myślę, że powinienem to sprawdzić wcześniej niż później. Jest całkiem możliwe, że GCC jest w porządku w nowszych wersjach - utknąłem w tym przypadku na "specjalnym" starym GCC. –

+0

Pozytywnie 'va_list' jest częścią ABI. Celem ABI jest zapewnienie, że wszystkie kompilatory dla danego docelowego CPU + OS generują kompatybilny kod, który obejmuje funkcje variadic. ... Niedawno widziałem kilka łatek dla systemu operacyjnego G64 dla GCC, więc może to dobrze działać z bieżącą wersją lub przynajmniej z drzewem programistycznym. – zwol

+0

@Zack, funkcje variadic działają dobrze - samo podanie 'va_list' nie działa. Dzięki za sprawdzenie zdrowia psychicznego - zrobię więcej testów i zobaczę, co się dzieje. –

0

Ponieważ istnieje nie ma standardu, w jaki sposób va_args musi być trzymany ręcznie, jeśli potrzebujesz tej funkcji, aby być spójnym na platformie skompilowanej krzyżowo, prawdopodobnie lepiej byłoby toczyć własną wersję. Nie zrobiliśmy tego i ostatnio wiele razy byliśmy spalani, ponieważ wspiera dodatkowe cele dla naszego kodu.Bardzo bym się mylił, gdyby inni mieli lepsze rozwiązanie :)

+0

OK, więc jak to zrobić roll mojej własnej wersji? Napisałem wersję, która działa dla IA32, ale gcc nie wydaje się rozlewać argumentów tak, jak chcę na Win64. –

0

Spróbuj uruchomić go przez debugger na poziomie zespołu, taki jak ollydbg, ponieważ Twój problem może nie występować w va_args, ale w sposób, w jaki kompilator jest spodziewając się, że argumenty zostaną przekazane (gcc może oczekiwać ich w formacie linuksowym, gdzie jako msvc używa się win64 __fastcall), jeśli cokolwiek to da nieco więcej światła do sytuacji. Innym, dość hackowskim podejściem jest próba manipulowania definicjami używanymi dla 64-bitowych argumentów, takich jak import makr msvc do nagłówka gcc (oczywiście użyj lokalnej kopii projektu), zobacz czy to naprawi wszystko.

+0

Variadyczne wywołania funkcji między modułami zbudowanymi z różnych kompilatorów, więc ABI jest pasujące Jest tylko Wewnętrzna implementacja 'va_list' która mnie gryzie. –

2

Ponieważ nie wydaje się, ABI dla va_list (lub przynajmniej MSVC i GCC nie zgadzam się na ABI), prawdopodobnie będziesz musiał samodzielnie sondować te parametry.

Najprostszym sposobem, w jaki mogę obejść ten problem z góry mojej głowy, jest ustawienie parametrów zmiennych w dynamicznie przydzielonym bloku pamięci i przekazanie wskaźnika do tego bloku.

Oczywiście ma to tę wadę, że całkowicie zmienia interfejs na funkcje, które obecnie używają va_args.

+0

Ale musiałbym wiedzieć, ile jest tam * parametrów * do wbicia ich w blok dynamiczny. Oznacza to, że każda funkcja, która chce wykonać 'va_start', a następnie wywołać inną funkcję z' va_list', musi wiedzieć, jak je zidentyfikować - w przypadku funkcji 'printf', oznacza to dużo dodatkowej analizy, gdzie cały punkt przejścia 'va_list' ma na celu skonsolidowanie całego tego parsowania ciągu formatów w jednej funkcji (np.' vsprintf'). –

-1

Jakiego rodzaju używasz? Ściśle mówiąc, WinXX definiuje zachowanie tylko znaków, ciągów, wskaźników i liczb całkowitych dla varargs (see documentation for wsprintf in user32.dll). Tak więc, jeśli przekażesz wartości zmiennoprzecinkowe lub struktury, wyniki są technicznie nieokreślone dla platformy Windows.

+0

W tym projekcie nie ma żadnego elementu zmiennoprzecinkowego, więc powinno być w porządku. Byłbym zaskoczony, gdyby struktury zaczęły się rozrzucać, ale mogę się temu przyjrzeć. Na ogół drukowane są tylko typy całkowite i łańcuchy. –

+0

Myślę, że ta odpowiedź jest po prostu błędna, btw ... że 'user32.dll' wersja' wsprintf' nie wspomina o "% f", a znajomi w jej dokumentacji nie * nie * oznaczają, że nie możesz przekazywać zmiennoprzecinkowe poprzez varargs na win32 w ogóle. A standard C wymaga przepuszczania zarówno zmiennoprzecinkowej, jak i strukturalnej przez varargs do działania. – zwol

+0

@Zack, zaktualizowałem swoją odpowiedź, aby odzwierciedlić, że nie zostało to określone dla platformy Windows. Oznacza to, że nie należy przekazywać tych typów do wsprintf (...), który jest funkcją user32.dll. To nie znaczy nic dla języka zaimplementowanego na Windowsie, takiego jak C lub C++. Jeśli więc chcesz kompatybilność między kompilatorami, powinieneś trzymać się typów, które platforma zmusza do wsparcia. W tym przypadku masz szczęście, że wsprintf został wyeksportowany z pliku user32.dll. – MSN