Стандарты C и C++ не предъявляют никаких требований к тому, как это должно работать. Соответствующий компилятор вполне может решить испускать цепочечные списки, std::stack<boost::any>
или даже волшебную пони-пыль (согласно комментарию @ Xeo) под капотом.
Однако обычно он реализуется следующим образом, хотя такие преобразования, как встраивание или передача аргументов в регистры ЦП, могут не оставить ничего из обсуждаемого кода.
Также обратите внимание, что этот ответ конкретно описывает растущий вниз стек на изображениях ниже; кроме того, этот ответ является упрощением только для демонстрации схемы (см. https://en.wikipedia.org/wiki/Stack_frame).
Как можно вызвать функцию с нефиксированным числом аргументов
Это возможно, потому что базовая архитектура машины имеет так называемый «стек» для каждого потока. Стек используется для передачи аргументов функциям. Например, когда у вас есть:
foobar("%d%d%d", 3,2,1);
Затем это компилируется в код на ассемблере, подобный этому (пример и схематично, фактический код может выглядеть иначе); обратите внимание, что аргументы передаются справа налево:
push 1
push 2
push 3
push "%d%d%d"
call foobar
Эти push-операции заполняют стек:
[] // empty stack
-------------------------------
push 1: [1]
-------------------------------
push 2: [1]
[2]
-------------------------------
push 3: [1]
[2]
[3] // there is now 1, 2, 3 in the stack
-------------------------------
push "%d%d%d":[1]
[2]
[3]
["%d%d%d"]
-------------------------------
call foobar ... // foobar uses the same stack!
Нижний элемент стека называется «Вершина стека», часто сокращенно «TOS».
Функция foobar
теперь будет обращаться к стеку, начиная с TOS, то есть строки формата, которая, как вы помните, была помещена последней. Представьте, что stack
— это ваш указатель стека, stack[0]
— это значение в TOS, stack[1]
— это значение выше TOS и так далее:
format_string <- stack[0]
... а затем анализирует строку формата. При синтаксическом анализе он распознает %d
-токенов и для каждого загружает еще одно значение из стека:
format_string <- stack[0]
offset <- 1
while (parsing):
token = tokenize_one_more(format_string)
if (needs_integer (token)):
value <- stack[offset]
offset = offset + 1
...
Это, конечно, очень неполный псевдокод, который демонстрирует, как функция должна полагаться на переданные аргументы, чтобы узнать, сколько ей нужно загрузить и удалить из стека.
Безопасность
Эта зависимость от предоставленных пользователем аргументов также является одной из самых серьезных проблем с безопасностью (см. https://cwe.mitre.org/top25/). Пользователи могут легко использовать вариационную функцию неправильно, либо потому, что они не читали документацию, либо забыли настроить строку формата или список аргументов, либо потому, что они просто злые, или что-то еще. См. также Атака на формат строки.
C Реализация
В C и C++ функции с переменным числом переменных используются вместе с интерфейсом va_list
. Хотя размещение в стеке свойственно этим языкам (в K+RC вы можете даже предварительно объявить функцию, не указывая ее аргументы, но по-прежнему вызывать ее с любым числом и типом аргументов), чтение из такого неизвестного списка аргументов осуществляется через интерфейс va_...
-макросы и va_list
-тип, которые в основном абстрагируют низкоуровневый доступ к кадрам стека.
person
Sebastian Mach
schedule
16.04.2014
You just copy&pasted the answer. So, this question is duplicate of other.
Это нелогично. Дублирующиеся ответы не делают дубликаты вопросов. - person Lightness Races in Orbit   schedule 16.04.2014