Някой има ли справка за представянето на va_list
в x86_64 ABI (този, който се използва в Linux)? Опитвам се да отстраня грешки в някакъв код, където стекът или аргументите изглеждат повредени и наистина би помогнало да разбера какво трябва да виждам...
Какъв е форматът на структурата x86_64 va_list?
Отговори (3)
Превърнах коментара си в отговор.
Това може да помогне . Това е препратка, макар и лека (РЕДАКТИРАНЕ: оригиналната връзка е мъртва; заменена връзка, запазена от Wayback Machine).
Препратката към списъка с променливи аргументи започва на страница 50, след което продължава, страници 52-53 документи va_list
:
Тип va_list
Типът va_list е масив, съдържащ единичен елемент от една структура, съдържаща необходимата информация за прилагане на макроса va_arg. C дефиницията на типа va_list е дадена на фигура 3.34
// Figure 3.34
typedef struct {
unsigned int gp_offset;
unsigned int fp_offset;
void *overflow_arg_area;
void *reg_save_area;
} va_list[1];
Макрото va_start
Макросът va_start инициализира структурата, както следва:
reg_save_area
Елементът сочи към началото на областта за запис на регистъра.
overflow_arg_area
Този указател се използва за извличане на аргументи, предадени на стека. Той се инициализира с адреса на първия аргумент, предаден на стека, ако има такъв, и след това винаги се актуализира, за да сочи към началото на следващия аргумент на стека.
gp_offset
Елементът съдържа отместването в байтове от reg_save_area до мястото, където е записан следващият наличен регистър на аргументи с общо предназначение. В случай, че всички регистри на аргументи са изчерпани, се задава стойност 48 (6 ∗ 8).
fp_offset
Елементът съдържа отместването в байтове от reg_save_area до мястото, където е записан следващият наличен регистър на аргументи с плаваща запетая. В случай, че всички регистри на аргументи са изчерпани, се задава стойност 304 (6 ∗ 8 + 16 ∗ 16).
Оказва се, че проблемът е, че gcc прави va_list
тип масив. Моята функция беше на подписа:
void foo(va_list ap);
и исках да предам указател към ap
към друга функция, така че направих:
void foo(va_list ap)
{
bar(&ap);
}
За съжаление, типовете масиви се разпадат до типове указатели в списъците с аргументи на функцията, така че вместо да подавам указател към оригиналната структура, предавах указател към указател.
За да заобиколя проблема, промених кода на:
void foo(va_list ap)
{
va_list ap2;
va_copy(ap2, ap);
bar(&ap2);
va_end(ap2);
}
Това е единственото преносимо решение, което можах да измисля, което отчита както възможността va_list
да е тип масив, така и възможността да не е.
foo
да вземе аргумент от тип va_list *
?
- person caf; 11.02.2011
foo
има фиксиран подпис, който не мога да променя. И дори ако не, v*
функциите винаги приемат va_list
аргумент, а не va_list *
аргумент. Това е стандартна конвенция и би било досадно за потребителите на функцията да я нарушават.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
bar
използва допълнителна информация, за да определи типа на следващия аргумент и го изважда от списъка с аргументи. Ако сте предали ap
по стойност, вместо да подадете указател към него, всяко по-нататъшно използване на ap
след bar
връща, с изключение на va_end
, ще доведе до недефинирано поведение.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
va_copy
.
- person bdonlan; 11.02.2011
bar
се извиква повече от веднъж от foo
и всяко повикване към bar
трябва да види ефектите от предишното. Това е изрично UB (по ISO C), ако преминете va_list
; от вас се изисква да подадете указател към va_list
за тази употреба.
- person R.. GitHub STOP HELPING ICE; 11.02.2011
va_copy()
се използва с аргумент, който няма тип va_list
: ако va_copy()
не е функция (което е изрично разрешено), тогава няма да се извършват корекции на параметри (!); все пак може да се твърди, че тъй като стандартът дава прототип за va_copy()
и посочва, че той може да бъде имплементиран като функция, имплицитно се приемат корекции на аргументи...
- person Christoph; 08.11.2011
va_list
, защото va_list
е тип масив и че това прави използването на va_copy
недефинирано? За какви настройки на параметрите говориш? Разпадането на типове масиви към указатели няма нищо общо с предаването им на функция...
- person R.. GitHub STOP HELPING ICE; 09.11.2011
va_copy
е да го използвате върху va_list
, което сте получили като аргумент. Ако сте го получили с va_start
, можете просто да се обадите на va_start
отново, за да получите копие и няма да има нужда от va_copy
.
- person R.. GitHub STOP HELPING ICE; 02.08.2013
va_copy
. Все още получавате va_list
, така че защо изведнъж е добре да подадете показалеца към копието? РЕДАКТИРАНЕ: Сега разбрах основната идея... julio.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
- person polynomial_donut; 25.07.2018
va_list
, което сте получили като аргумент, към друга функция, приемаща va_list
(стига да не го използвате след това, освен към va_end
). Също така е добре да подадете указател към va_list
....
- person R.. GitHub STOP HELPING ICE; 25.07.2018
va_list ap;
ви дава обект от тип va_list
, така че &ap
да има тип указател-към-va_list
, получаването на аргумент, деклариран va_list ap
не е задължително< /b> означава ap
има тип va_list
. Ако va_list
е тип масив, се прилагат правилата за разпадане на масива за аргументите на функцията и тогава ap
има тип указател-към-указател-към-__typeof__(*ap)
, който не е нито съвместим с типа, нито правилната стойност за предаване на функция, очакваща указател към -va_list
(това е допълнително ниво на индиректност). va_copy
to temp е единственият начин да поправите това.
- person R.. GitHub STOP HELPING ICE; 25.07.2018
va_list
е добре, ако C99 ни каже това или не (?). Поне бих искал да предположа, че clang
или gcc
са внедрили C99 според пълните спецификации (?).
- person polynomial_donut; 25.07.2018
sizeof
към него и очакването на размера на целия обект с данни - но това няма да работи дори ако не предаде указател към типа на масива и не го дереферира (и просто предаде самия масив) (?)
- person polynomial_donut; 25.07.2018
va_list
към друга функция, така че такава реализация, която използва относителни отмествания от база, която може да знае само функцията, извикваща va_start
, би била несъответстваща.
- person R.. GitHub STOP HELPING ICE; 25.07.2018
va_list ap
, където va_list
е дефиниран с тип масив, ap
няма тип va_list
. Има тип __typeof__(*ap)*
. Масивите не са указатели. Следователно &ap
няма тип va_list *
(указател към целия масив, чиято стойност е същата като указателя към началния член, но чийто тип е различен), който трябва да има за да го предадете на функция, очакваща va_list *
. Вместо това &ap
има тип __typeof__(*ap)**
, тип указател към указател, чиято стойност няма нищо общо с адреса на обекта va_list
.
- person R.. GitHub STOP HELPING ICE; 25.07.2018
foo
, ap
се преобразува в typeof(*ap) *
(съгласно C99) 2. След това bar
се извиква на &ap
, което е от тип typeof(*ap) **
3. Ако приемем, че полученият аргумент вътре в bar
е именуван ap_bar
: Ако ap_bar
се дереферира, резултатът е действителен масив, така че машинният код ще работи по различен начин върху него, когато използвате []
или *
, отколкото ако беше __typeof__(*ap) **
(което наистина е във вашия пример).
- person polynomial_donut; 25.07.2018
ap_bar *
подлежи на същите преобразувания на аргументи (масив в указател), както е споменато в точка 1; това преобразуване може да доведе до неправилни резултати, когато се приложи към променлива от тип __typeof__(*ap) *
.
- person polynomial_donut; 25.07.2018
typedef int foo[1];
и функции, приемащи този тип foo
или указател към него като аргумент.
- person R.. GitHub STOP HELPING ICE; 25.07.2018
В i386 архитектурата va_list е тип указател. В архитектурата AMD64 обаче това е тип масив. Каква е разликата? Всъщност, ако приложите операция & към тип указател, ще получите адреса на тази променлива на указателя. Но без значение колко пъти прилагате & операция към тип масив, стойността е същата и е равна на адреса на този масив.
И така, какво трябва да направите в AMD64? Най-лесният начин за предаване на променлива на va_list във функция е просто да я подадете без * или & оператор.
Например:
void foo(const char *fmt, ...) {
va_list ap;
int cnt;
va_start(ap, fmt);
bar(fmt, ap);
va_end(ap);
return cnt;
}
void bar(const char *fmt, va_list ap) {
va_arg(ap, int);
//do something
test(ap);
}
void test(va_list ap) {
va_arg(ap, int);
//do something
}
Просто работи! И не е нужно да се притеснявате колко аргументи имате.