Для чего нужен va_end? Всегда ли нужно называть это?

va_end - макрос до reset arg_ptr.

После доступа к списку аргументов переменных указатель arg_ptr обычно reset с va_end(). Я понимаю, что это требуется, если вы хотите переименовать список, но действительно ли это необходимо, если вы этого не сделаете? Это просто хорошая практика, как правило "всегда есть default: в вашем switch"?

Ответ 1

va_end используется для очистки. Вы не хотите разбивать стек, не так ли?

От man va_start:

va_end()

Каждый вызов va_start() должен быть сопоставлен соответствующим вызовом va_end() в той же функции. После вызова va_end (ap) переменная ap undefined. Возможны множественные обходы списка, каждая из которых заключена в скобки с помощью va_start() и va_end(). va_end() может быть макросом или функцией.

Обратите внимание на наличие слова must.

Стек может стать поврежденным, потому что вы не знаете, что делает va_start(). Макросы va_* предназначены для обработки как черные ящики. Каждый компилятор на каждой платформе может делать все, что захочет. Он ничего не может сделать или может многое сделать.

Некоторые ABI передают первые несколько аргументов в регистры, а остальные - в стеке. A va_arg() может быть сложнее. Вы можете посмотреть, как данная реализация выполняет varargs, что может быть интересно, но при написании переносного кода вы должны рассматривать их как непрозрачные операции.

Ответ 2

В Linux x86-64 только один обход можно выполнить с помощью переменной va_list. Чтобы сделать больше обходов, его нужно скопировать, используя va_copy. man va_copy объясняет детали:

va_copy()

Очевидная реализация будет иметь va_list, указатель на    стековый кадр вариационной функции. В такой установке (безусловно, наиболее    общий) нет ничего против присвоения

   va_list aq = ap;

К сожалению, есть также системы, которые делают его массивом указателей    (длины 1), и там нужно

   va_list aq;
   *aq = *ap;

Наконец, в системах, где аргументы передаются в регистры, это может быть    необходимо для va_start() распределять память, хранить там аргументы,    а также указание того, какой аргумент следующий, так что va_arg() может    перейдите в список. Теперь va_end() может освободить выделенную память    еще раз. Чтобы учесть эту ситуацию, C99 добавляет макрос va_copy(), поэтому    что вышеупомянутое назначение можно заменить на

   va_list aq;
   va_copy(aq, ap);
   ...
   va_end(aq);

Каждый вызов va_copy() должен быть сопоставлен соответствующей invoca-    va_end() в той же функции. Некоторые системы, которые не поставляют    va_copy() вместо этого имеют __va_copy, так как это имя было использовано в    проект предложения.

Ответ 3

В общей реализации "параметров, переданных в стек", я считаю, что va_end() обычно ничего/пусто/null. Однако на платформах, которые имеют менее традиционные схемы, это становится необходимым. Это "хорошая практика", чтобы включить его в нейтральную платформу.