Как да определим края на списъка va_arg?

Имам функция foo(char *n, ...); Трябва да получа и използвам всички незадължителни char параметри. Имах идея да използвам

while(va_arg(argPtr, char) != NULL)
{
   ...
}

да разбера кога стигна до края на списъка. И така, ще работи ли, ако при извикване на функция ще направя foo(n, 't', 'm', '$', NULL);?

Ще се чете ли NULL като char от va_arg? Или може би има по-разпространен начин за определяне на края на списъка, без добавяне на NULL като последен параметър?


person kekyc    schedule 20.05.2016    source източник
comment
ако n е форматиращ низ, тогава обикновено бихте се държали като printf и семейство, като съпоставите броя на спецификаторите на формат с броя на параметрите. Сигурни ли сте, че се предава само променлив брой chars? Ако е така, можете да съпоставите -1 или 0 или нещо подобно, но NULL е лоша идея поради причините, посочени от @hvd в неговите коментари по-долу.   -  person Ryan Haining    schedule 21.05.2016
comment
@kekyc, ако разчитате на отрицателни char стойности, тогава трябва да използвате signed char, ако имате работа само със стойности в диапазона [0, 128) (ascii), тогава -1 трябва да е добре.   -  person Ryan Haining    schedule 21.05.2016
comment
Ако предавате списък с променлива дължина от char аргументи, защо просто не подадете низ? (Има някои случаи, в които това няма да работи, но това е очевидното решение.)   -  person Keith Thompson    schedule 21.05.2016
comment
Ако имате променлив брой параметри от един и същи тип, завършващи със специална стойност, тогава тази стойност трябва да съответства на типа на аргументите. Ако подавате int стойности, тогава трябва да изберете някаква специална int стойност като 0 или -1. Ако предавате низове, т.е. char *, тогава трябва да го прекратите с (char *) NULL, както например изисква execl().   -  person Tom Karzes    schedule 21.05.2016
comment
Добре, благодаря ви, момчета, за страхотните, пълни отговори!   -  person kekyc    schedule 21.05.2016


Отговори (2)


Няма директен начин за функция, която използва va_arg за определяне на броя или типа(овете) на аргументите, предадени от дадено извикване.

Предложеният от вас метод по-специално:

while(va_arg(argPtr, char) != NULL)

е неправилно. va_arg(argPtr, char) дава стойност от тип char, докато NULL е константа с нулев указател. (NULL обикновено се дефинира като 0, което се сравнява с нулевия знак '\0', но не можете да разчитате на това.)

Всяка променлива функция трябва да има начин за извикващия да указва броя и типовете аргументи. Функциите *printf, например, правят това чрез (непроменлив) форматиращ низ. Функциите POSIX execl*() очакват поредица от char* аргументи; краят на списъка с аргументи се маркира от извикващия с (char*)NULL. Възможни са и други методи, но те почти всички зависят от информацията, дадена по време на изпълнение в аргументите. (Вие можете да използвате някакъв друг метод, като например глобална променлива. Моля, недейте.)

Това натоварва извикващия да гарантира, че аргументите, предадени на функцията, са последователни. Самата функция няма начин да потвърди това. Неправилните повиквания, като printf("%d\n", "hello") или execlp("name", "arg1"), имат недефинирано поведение.

Още нещо: не можете да използвате va_arg с аргумент от тип char. Когато извиквате променлива функция, аргументите, съответстващи на , ..., се повишават. Целочислените аргументи от типове, по-тесни от int, се повишават до int или unsigned int, а аргументите от тип float се повишават до double. Ако повикващият предаде аргумент от тип char, функцията трябва да извика va_arg(argPtr, int).

(При много неясни обстоятелства, на които е малко вероятно да попаднете, char може да бъде повишен до unsigned int. Това може да се случи само ако обикновеният char е без знак и sizeof (int) == 1, което означава, че един байт е поне 16 бита.)

person Keith Thompson    schedule 20.05.2016
comment
Много хубаво и пълно. Неясните методи включват глобална променлива или нещо сред аргументите (не непременно краен страж). И все пак двата начина, споменати в този прекрасен отговор, са най-често срещаните. - person chux - Reinstate Monica; 21.05.2016
comment
@chux: Добра гледна точка за глобалните променливи. Добавих подходящи думи за невестулка. - person Keith Thompson; 21.05.2016
comment
Спомням си тези слаби практики за кодиране от дните, когато foo(...) беше разрешено - преди C89. - person chux - Reinstate Monica; 21.05.2016
comment
@chux: Интересно. Не знаех, че променливите функции преди ANSI (вероятно използващи <varargs.h> за извличане на техните аргументи) използват ... синтаксиса. - person Keith Thompson; 21.05.2016
comment
Дори някои C99 компилатори позволяваха нула аргументи като разширение. Ако синтаксисът беше ... или не, може да съм запомнил неправилно - но въпросът е, че нула аргументи са били кодирани в даден момент от предаването като разширение и/или пре -C89. Рядко беше полезно. - person chux - Reinstate Monica; 21.05.2016
comment
NULL не се ли тълкува изрично като ((void* )0x0). Присвояването му на целочислен тип трябва да доведе до предупреждение. - person Edenia; 31.03.2019
comment
@Edenia: В C, NULL се разширява до дефинирана от имплементацията константа на нулев указател. Константата с нулев указател се дефинира като израз с целочислена константа със стойност 0 или такъв израз, преобразуван в typevoid *. Така че NULL може да се разшири до всяко от ((void*)0), или 0, или ('-'-'-'), ако внедрителят е в извратено настроение. Дали int n = NULL; ще създаде диагностика зависи от внедряването. (Имайте предвид, че C++ има различни правила.) - person Keith Thompson; 31.03.2019

Основната идея би проработила. Но вие сте попълнили подробностите по начин, който почти сигурно няма да го направи.

Обичайните неявни правила за преобразуване не се прилагат, когато използвате променливи аргументи. Вместо това се провеждат популяризации на аргументи по подразбиране, което означава, че всеки тип цяло число, по-малък от int/unsigned int, се преобразува в един от тях -- това не е единственото повишение, но единственото подходящо тук -- и което също означава, че няма автоматично преобразуване към какъвто и тип да посочите с va_arg.

Това означава, че:

  • Никога не можете да използвате va_arg(..., char), тъй като е невъзможно да прехвърлите char като аргумент на променлива функция.
  • Никога не можете да предавате NULL като променлив аргумент на функцията, тъй като конкретният му тип е силно зависим от изпълнението. Реалистичните типове са int, long, void * и има много други по-малко реалистични, но също толкова валидни типове.

Можеш да се промениш

while(va_arg(argPtr, char) != NULL)

to

while(va_arg(argPtr, int) != 0)

и обаждането

foo(n, 't', 'm', '$', NULL);

to

foo(n, 't', 'm', '$', 0);
person Community    schedule 20.05.2016