Безопасно ли использовать макрос va_start с этим параметром?
Я должен использовать компилятор IAR во встроенном приложении (у него нет пространств имен, исключений, множественного/виртуального наследования, шаблоны ограничены по битам и поддерживается только С++ 03). Я не могу использовать пакет параметров, поэтому я попытался создать функцию-член с переменным параметром. Я знаю, что переменные параметры, как правило, небезопасны. Но безопасно ли использовать this
указатель в макросе va_start
?
Если бы я использовал обычную функцию с переменным числом аргументов, то перед тем ...
чтобы получить доступ к остальным параметрам, понадобился бы фиктивный параметр. Я знаю, что макросу variadic не нужен параметр раньше ...
но я бы предпочел не использовать его. Если я использую функцию-член, она скрывала this
параметр раньше ...
поэтому я попробовал:
struct VariadicTestBase{
virtual void DO(...)=0;
};
struct VariadicTest: public VariadicTestBase{
virtual void DO(...){
va_list args;
va_start(args, this);
vprintf("%d%d%d\n",args);
va_end(args);
}
};
//Now I can do
VariadicTestBase *tst = new VariadicTest;
tst->DO(1,2,3);
tst->DO(1,2,3);
печатает 123, как и ожидалось. Но я не уверен, что это не просто случайное/неопределенное поведение. Я знаю tst->DO(1,2);
рухнул бы так же, как обычный принф. Я не возражаю.
Ответы
Ответ 1
Ничто не определяет это поведение в стандарте, поэтому эта конструкция просто вызывает формальное неопределенное поведение. Это означает, что он может нормально работать в вашей реализации и вызвать ошибку компиляции или неожиданные результаты в другой реализации.
Тот факт, что нестатические методы должны передавать скрытый указатель this
не может гарантировать, что va_start
сможет его использовать. Вероятно, это работает именно так, потому что в ранние времена компиляторы C++ были просто препроцессорами, которые преобразовывали источник C++ в источник C, а скрытый параметр this
был добавлен препроцессором для доступности компилятору C. И это, вероятно, будет поддерживаться в целях совместимости. Но я бы постарался избежать этого в критически важном коде...
Ответ 2
Я думаю, что все должно быть в порядке, хотя я сомневаюсь, что вы найдете конкретную цитату из стандарта C++, которая так говорит.
Обоснование таково: va_start()
должен быть передан последний аргумент функции. Функция-член, не имеющая явных параметров, имеет только один параметр (this
), который, следовательно, должен быть ее последним параметром.
Будет легко добавить unit тест, чтобы предупредить вас, если вы когда-либо будете компилировать на платформе, где это не работает (что кажется маловероятным, но опять же вы уже компилируете на несколько нетипичной платформе).
Ответ 3
Кажется, неопределенное поведение. Если вы посмотрите на то, что va_start(ap, pN)
делает во многих реализациях (проверьте файл заголовка), он берет адрес pN, увеличивает указатель на размер pN и сохраняет результат в ap. Можем ли мы юридически посмотреть на &this
?
Я нашел хорошую ссылку здесь: fooobar.com/questions/588416/...
Цитируя стандарт 2003 C++:
5.1 [expr.prim] Ключевое слово this называет указатель на объект, для которого вызывается нестатическая функция-член (9.3.2).... Тип выражения является указателем на класс функций (9.3.2),... Выражение является значением r.
5.3.1 [expr.unary.op] Результатом унарного оператора & является указатель на его операнд. Операндом должно быть lvalue или qual_id.
Таким образом, даже если это работает для вас, это не гарантируется, и вы не должны полагаться на это.
Ответ 4
Это неопределенное поведение. Поскольку язык не требует this
, чтобы быть передан в качестве параметра, он не может быть принят вообще.
Например, если компилятор может выяснить, что объект является одноэлементным, он может избежать передачи this
в качестве параметра и использовать глобальный символ, когда адрес this
явно требуется (как в случае va_start). Теоретически, компилятор может генерировать код, чтобы компенсировать его в va_start
(в конце концов, компилятор знает, что это одиночный код), но это не требуется стандартом.
Подумайте о чем-то вроде:
class single {
public:
single(const single& )= delete;
single &operator=(const single& )= delete;
static single & get() {
// this is the only place that can construct the object.
// this address is know not later than load time:
static single x;
return x;
}
void print(...) {
va_list args;
va_start (args, this);
vprintf ("%d\n", args);
va_end (args);
}
private:
single() = default;
};
Некоторые компиляторы, такие как clang 8.0.0, выдают предупреждение для приведенного выше кода:
prog.cc:15:23: warning: second argument to 'va_start' is not the last named parameter [-Wvarargs] va_start (args, this);
Несмотря на предупреждение, все работает нормально. В общем, это ничего не доказывает, но иметь предупреждение - плохая идея.
Примечание: я не знаю ни одного компилятора, который бы обнаруживал синглтоны и обрабатывал их специально, но язык не запрещает этот вид оптимизации. Если это не сделано сегодня вашим компилятором, это может быть сделано завтра другим компилятором.
Примечание 2: несмотря на все это, на практике это может сработать, чтобы передать это в va_start
. Даже если это работает, не стоит делать что-то, что не гарантировано стандартом.
Примечание 3: та же оптимизация синглтона не может применяться к таким параметрам, как:
void foo(singleton * x, ...)
Его нельзя оптимизировать, так как он может иметь одно из двух значений, указывать на синглтон или быть nullptr
. Это означает, что эта проблема оптимизации здесь не применима.