Безопасно ли использовать макрос va_start с этим параметром? - программирование

Безопасно ли использовать макрос 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); рухнул бы так же, как обычный принф. Я не возражаю.

4b9b3361

Ответ 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. Это означает, что эта проблема оптимизации здесь не применима.