Безопасно ли использовать макрос va_start с этим параметром? - PullRequest
18 голосов
/ 17 апреля 2019

Мне нужно использовать IAR-компилятор во встроенном приложении (у него нет пространств имен, исключений, множественного / виртуального наследования, шаблоны ограничены по битам и поддерживается только C ++ 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); зависнет так же, как и обычный принф. Я не против этого.

Ответы [ 4 ]

20 голосов
/ 17 апреля 2019

Ничто не определяет это поведение в стандарте, поэтому эта конструкция просто вызывает формальное неопределенное поведение.Это означает, что он может нормально работать в вашей реализации и вызвать ошибку компиляции или непредвиденные результаты в другой реализации.

Тот факт, что нестатические методы должны передавать скрытый указатель this, не может гарантировать, что va_start можетиспользуй это.Вероятно, это работает именно так, потому что в ранние времена компиляторы C ++ были просто препроцессорами, которые конвертировали исходники C ++ в исходники C, а препроцессор добавлял скрытый параметр this, чтобы он был доступен для компилятора C.И это, вероятно, поддерживается по причинам совместимости .Но я бы постарался избежать этого в критически важном коде ...

3 голосов
/ 17 апреля 2019

Кажется, что неопределенное поведение.Если вы посмотрите на то, что va_start(ap, pN) делает во многих реализациях (проверьте файл заголовка), он берет адрес pN, увеличивает указатель на размер pN и сохраняет результат в ap.Можем ли мы на законных основаниях взглянуть на &this?

Я нашел хорошую ссылку здесь: https://stackoverflow.com/a/9115110/10316011

Цитирование стандарта C ++ 2003 года:

5.1 [expr.prim] Ключевое слово this называет указатель на объект, для которого вызывается нестатическая функция-член (9.3.2).... Тип выражения является указателем на класс функции (9.3.2), ... Выражение является значением r.

5.3.1 [expr.unary.op] Результатунарный оператор & является указателем на его операнд.Операндом должно быть lvalue или qual_id.

Так что даже если это работает для вас, это не гарантируется, и вы не должны полагаться на него.

2 голосов
/ 17 апреля 2019

Я думаю, что все должно быть в порядке, хотя я сомневаюсь, что вы найдете конкретную цитату из стандарта C ++, в которой говорится так.

Обоснование таково: va_start() должен быть передан последний аргумент функции. Функция-член, не имеющая явных параметров, имеет только один параметр (this), который, следовательно, должен быть ее последним параметром.

Будет легко добавить модульный тест, чтобы предупредить вас, если вы когда-нибудь будете компилировать на платформе, где это не работает (что кажется маловероятным, но опять же вы уже компилируете на несколько нетипичной платформе).

0 голосов
/ 17 апреля 2019

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

...