2015-08-29 3 views
4

Для любопытства я ищу, чтобы написать минимальные замены для некоторых функций в стандартной библиотеке C. До сих пор я закончил printf(), strlen(), strcpy(), memcpy(), memset() и т. Д., Но когда я пытаюсь использовать функцию printf, я не знаю, как реализовать stdarg.h! Что я могу сделать?Как реализовать stdarg в C

Использует ли он макросы или фактические функции?

Я использую gcc ИЛИ clang на 32-разрядном x86, если это помогает сделать это легче.

+3

Почему бы не просто заглянуть в ** заголовок **, предоставленный вашим предпочтительным компилятором? Обратите внимание, что это специфично для реализации. Любой всеобъемлющий ответ был бы слишком широким. Пожалуйста, будьте более конкретными. – Olaf

+0

@Olaf Я искал исходный код gcc, clang и glibc в течение 2 часов, и я не мог найти тот, который даже удаленно выглядит как реализация. –

+0

Пожалуйста, внимательно прочитайте первое предложение моего комментария. Собственно, прочитайте весь комментарий. – Olaf

ответ

3

На 32-битной x86 с Cdecl вызова конвенции, параметры передаются в стек:

^ higher addresses (lower on the stack) 
| 
| caller local variables 
| ... 
| argument 3 
| argument 2 
| argument 1 
| return address 
| saved EBP (usually) 
| callee local variables 
| 
v lower addresses (higher on the stack) 

Вы можете реализовать va_list как указатель. va_start может передать адрес переданного ему аргумента и добавить размер этого аргумента для перехода к следующему аргументу. va_arg может получить доступ к указателю и подвести его к следующему аргументу. va_copy может просто скопировать значение указателя. va_end ничего не нужно делать.

Если, с другой стороны, вы не используете cdecl (возможно, вы используете fastcall), вы не 32-битный, вы не x86, это не сработает; вам может понадобиться гасить регистры, а не только значения указателя. И даже это еще не гарантировано, потому что вы полагаетесь на неопределенное поведение; как пример только одной потенциальной проблемы, вложение может испортить все. И вот почему заголовочный файл просто typedef s это для компилятора, встроенного в реализацию этого в C, безнадежно, вам нужна поддержка компилятора. И даже не заставляйте меня начинать с внедрения setjmp и longjmp ...

+0

Потому что это практически невозможно реализовать «правильно» без написания компилятора (и я не знаю, как это сделать), это лучшее решение для связи с libgcc? –

+0

@AnonymousShadow: сначала попробуйте ссылку, не включая 'libgcc'; поскольку 'va_ *' - это макросы, которые ссылаются на встроенные модули, они не могут добавлять ссылки на внешние функции, и, следовательно, вам может не понадобиться вообще связать что-либо лишнее. Тем не менее, если вы получаете неопределенные ошибки символов для символов, которые определяет 'libgcc', да, я думаю, вам нужно будет связать это. – icktoofay

+2

Этот ответ неверен; он предполагает древнюю технологию компилятора, где адрес объектов аргумента фактически будет адресом, которым они были (или были бы) переданы в стек. В этом нет причин. Даже на таких арках, как i386, где аргументы внешних функций передаются в стеке, компилятор полностью может перемещать их в отдельное локальное хранилище и «&» дает указатель на это новое хранилище. Также можно выполнить inlining/LTO, и в этом случае аргументы могут вообще не существовать в стеке. –

2

Вы можете увидеть примеры реализации макросов va here. Этот заголовок используется в VC++, и для каждой архитектуры процессора существуют разные реализации. Макросы не кажутся специфичными для компилятора Microsoft. В GCC и Clang макросы va ссылаются на встроенные функции компилятора.

3

Невозможно реализовать макросы stdarg.h в C; вам нужны компиляторы, такие как __builtin_va_arg и т. д., которые GCC и совместимые компиляторы предоставляют или эквивалент для вашего компилятора.

Даже если вы знаете соглашение о передаче аргументов для конкретной цели, с которой работаете (например, i386 в ответе icktoofay), на C нет доступа к этой памяти. Недостаточно выполнить арифметику указателя по адресу, адресуемому va_start; это приводит к неопределенному поведению. Но даже если C разрешило эту арифметику, нет никакой гарантии, что адрес последнего именованного аргумента фактически соответствует месту, которое он передал в стеке как часть соглашения о вызове; компилятор мог бы переместить его в другое место в своем стеке стека (возможно, ради получения дополнительного выравнивания или локализации данных).

+0

Это зависит от архитектуры PCS/ABI. Макросы могут очень заботиться о выравнивании, поскольку они знают типы. Ранние реализации на самом деле ** использовали ** макросы для этого. Я имел их на 68000, например (IIRC, это было gcc 1.что-то, но не уверен). И, по крайней мере, переупорядочение будет портить вызовы между различными единицами компиляции. – Olaf

+0

@Olaf: Он не зависит от ABI. Даже если вы знаете, что args * переданы * в стек, вы не знаете, что '& argN' - это адрес слота стека, который он передал. Внутри тела функции 'argN' является обычным объектом автоматического хранения. Компилятор может свободно размещать его там, где он хочет (до тех пор, пока он копирует исходное значение из того места, куда его называет конвенция), и это чисто * внутреннее * рассмотрение того, как функция была скомпилирована, а не ABI. –

+0

Конечно, некоторые исторические компиляторы были настолько примитивны, что '& argN' всегда был слотом стека, который он передал, или что они намеренно предпочли оптимизировать, чтобы облегчить реализацию устаревших реализаций' stdarg.h'. Но в этом случае он работал только из-за предположений о несовместимом с ABI поведении компилятора, а не только при знании ABI. –

2

Там есть реализация в исходном коде CALC, публикуемом в comp.sources.unix в 1992 году

Это из архива шарпея, поэтому игнорировать X с.

X * Copyright (c) 1992 David I. Bell 
X * Permission is granted to use, distribute, or modify this source, 
X * provided that this copyright notice remains intact. 

X/* 
X * SIMULATE_STDARG 
X * 
X * WARNING: This type of stdarg makes assumptions about the stack 
X *    that may not be true on your system. You may want to 
X *   define STDARG (if using ANSI C) or VARARGS. 
X */ 
X 
Xtypedef char *va_list; 
X#define va_start(ap,parmn) (void)((ap) = (char*)(&(parmn) + 1)) 
X#define va_end(ap) (void)((ap) = 0) 
X#define va_arg(ap, type) \ 
X (((type*)((ap) = ((ap) + sizeof(type))))[-1]) 
Смежные вопросы