Предупреждение может быть удалено на объекте C ++ с помощью setjmp

#include <setjmp.h>
#include <vector>

int main(int argc, char**) {
 std::vector<int> foo(argc);
 jmp_buf env;
 if (setjmp(env)) return 1;
}

Компиляция приведенного выше кода с GCC 4.4.1, g ++ test.cc -Wextra -O1, дает это сбивающее с толку предупреждение:

/usr/include/c++/4.4/bits/stl_vector.h: In function ‘int main(int, char**)’:
/usr/include/c++/4.4/bits/stl_vector.h:1035: warning: variable ‘__first’ might be clobbered by ‘longjmp’ or ‘vfork’

Строка 1035 файла stl_vector.h находится во вспомогательной функции, используемой конструктором vector (n, value), который я вызываю при создании foo. Предупреждение исчезает, если компилятор может определить значение аргумента (например, это числовой литерал), поэтому я использую argc в этом тестовом примере, потому что компилятор не может определить его значение.

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

Как я могу избежать проблемы, желательно без необходимости передавать часть setjmp другой функции?

Не использовать setjmp - это не вариант, потому что я застрял с кучей библиотек C, которые требуют его использования для обработки ошибок.


person Tronic    schedule 08.01.2010    source источник
comment
Возможно, вам стоит опубликовать аргументы компиляции и версию gcc. Я не вижу никаких предупреждений в своей системе ...   -  person P Shved    schedule 08.01.2010
comment
возможный обман: stackoverflow.com/ questions / 1376085 /   -  person Jherico    schedule 08.01.2010


Ответы (4)


Правило состоит в том, что любая энергонезависимая нестатическая локальная переменная в фрейме стека, вызывающем setjmp , может быть сбита вызовом longjmp. Самый простой способ справиться с этим - убедиться, что фрейм, который вы вызываете setjmp, не содержит никаких таких переменных, которые вам нужны. Обычно это можно сделать, поместив setjmp в функцию отдельно и передав ссылки на вещи, которые были объявлены в другой функции, которая не вызывает setjmp:

#include <setjmp.h>
#include <vector>

int wrap_libcall(std::vector<int> &foo)
{
  jmp_buf env;
  // no other local vars
  if (setjmp(env)) return 1;
  // do stuff with your library that might call longjmp
  return 0;
}

int main(int argc, char**) { 
  std::vector<int> foo(argc);
  return wrap_libcall(foo);  
}

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

Изменить

Точная цитата из спецификации C99 на setjmp:

Все доступные объекты имеют значения, а все другие компоненты абстрактной машины имеют состояние на момент вызова функции longjmp, за исключением того, что значения объектов с автоматической продолжительностью хранения, которые являются локальными для функции, содержащей вызов соответствующего setjmp Макросы, которые не имеют изменчивого типа и были изменены между вызовом setjmp и вызовом longjmp, являются неопределенными.

person Chris Dodd    schedule 20.01.2010
comment
Спасибо, это соответствует тому, что я обнаружил при тестировании, и вы хорошо объяснили ситуацию. Я бы предпочел избежать разбиения кода на дополнительную функцию (например, заключив его в {} вместо этого, но это не сработало). Я задам вопрос еще раз, и если никто не сможет придумать лучшее решение, я отмечу это как принятый ответ. - person Tronic; 21.01.2010
comment
Кстати, есть ли риск того, что это тоже не удастся, если компилятор решит встроить wrap_libcall? Стандарт не определяет, что такое стековый фрейм. - person Tronic; 21.01.2010
comment
Спецификация C относится к объектам с автоматической продолжительностью хранения, которые являются локальными для функции, содержащей вызов setjmp, поэтому все, что находится за пределами wrap_libcall, должно быть безопасным, даже если оно встроено. Однако это кажется мне чем-то, что легко может быть подвержено ошибкам компилятора. - person Chris Dodd; 25.01.2010
comment
это все равно взорвется, если компилятор решит встроить wrap_libcall? - person paulm; 22.12.2015

Это не предупреждение, которое следует игнорировать, объекты longjmp () и C ++ не ладят друг с другом. Проблема в том, что компилятор автоматически вызывает деструктор для вашего объекта foo. Longjmp () может обойти вызов деструктора.

Исключения C ++ также разматывают кадры стека, но они гарантируют, что будут вызваны деструкторы локальных объектов. Нет такой гарантии от longjmp (). Чтобы узнать, будет ли longjmp () байтовым, вам необходимо тщательно проанализировать локальные переменные в каждой функции, которая может быть прервана раньше срока из-за longjmp (). Это непросто.

person Hans Passant    schedule 08.01.2010
comment
Я не думаю, что здесь происходит то, что вы описываете, поскольку foo определен до setjmp, так что никакие объекты не будут разрушены на longjmp. - person Tronic; 08.01.2010
comment
Опубликованный код дает ошибку. Настоящий код находится на git: //git.performous.org/gitroot/performous/performous (game / image.hh), но я не думаю, что он действительно помогает рассматривать ту же проблему в гораздо более сложной среде. - person Tronic; 18.01.2010

Как видно из строки 1035 в сообщении об ошибке, фрагмент кода значительно упростил фактический код проблемы. Вы зашли слишком далеко. Нет никакого представления о том, как вы используете слово «первый». Проблема в том, что компилятор не может понять этого даже в реальном коде. Он опасается, что значение first после ненулевого возврата от setjmp может оказаться не таким, как вы думаете. Это потому, что вы изменили его значение до и после первого вызова (нулевой возврат) на setjmp. Если переменная хранилась в регистре, значение, вероятно, будет другим, чем если бы оно хранилось в памяти. Итак, компилятор консервативен, давая вам предупреждение.

Чтобы сделать слепой шаг и ответить на вопрос, вы можете избавиться от предупреждающего сообщения, уточнив объявление «первый» с «изменчивым». Вы также можете попробовать сделать «первый» глобальным. Возможно, сбросив уровень оптимизации (флаг -O), вы можете заставить компилятор сохранять переменные в памяти. Это быстрые исправления, которые могут фактически скрыть ошибку.

Вам действительно стоит взглянуть на свой код и на то, как вы используете «first». Я сделаю еще одно безумное предположение и скажу, что вы можете исключить эту переменную. Может ли это имя, «первый», означать, что вы используете его для обозначения первого вызова (нулевого возврата) к «setjmp»? Если так, избавьтесь от этого - измените свою логику.

Если реальный код просто завершается ненулевым возвратом из 'setjmp' (как в фрагменте), тогда значение 'first' не имеет значения в этом логическом пути. Не используйте его по обе стороны от setjmp.

person gary    schedule 08.01.2010
comment
Упс - только что заметил, что ошибка относится к файлу библиотеки. Я предположил, что вы только что предоставили образец фрагмента, потому что я не получил предупреждения об этом коде и не видел причин для его появления. Возможно, вам стоит предоставить некоторую информацию о платформе и флаги компилятора. На самом деле больше вашего кода не требуется, чтобы вызывать предупреждение ....? - person gary; 08.01.2010

Быстрый ответ: сбросьте флаг -O1 или верните компилятор к более ранней версии. Любой из них заставил предупреждение исчезнуть в моей системе. Мне пришлось сначала собрать и использовать gcc4.4, чтобы получить предупреждение. (черт возьми, это огромная система)

Нет? Я думал, что нет.

Я действительно не понимаю всего, что C ++ делает со своими объектами, и как именно они освобождаются. Тем не менее, комментарий OP о том, что проблема не возникает, если вместо argc для размера вектора использовалось постоянное значение, дает мне возможность высунуть шею. Рискну предположить, что C ++ использует указатель __first при освобождении только тогда, когда начальное выделение не является константой. На более высоком уровне оптимизации компилятор больше использует регистры и возникает конфликт между выделениями до и после setjmp ... Не знаю, это не имеет смысла.

Общее значение этого предупреждения звучит так: «Вы уверены, что знаете, что делаете?» Компилятор не знает, знаете ли вы, каким будет значение '__first', когда вы выполните longjmp и получите ненулевой возврат от 'setjmp'. Вопрос в том, является ли его значение после (ненулевого) возврата значением, которое было помещено в буфер сохранения, или значением, которое вы создали после сохранения. В этом случае это сбивает с толку, потому что вы не знали, что используете '__first', и потому что в такой простой программе нет (явного) изменения на '__first'

Компилятор не может анализировать логический поток в сложной программе, поэтому он, по-видимому, даже не пытается выполнить какую-либо программу. Это допускает возможность того, что вы действительно изменили значение. Так что это просто дружеское предупреждение. Компилятор второй раз вас догадывается, пытаясь быть полезным.

Если вы упорствуете в выборе компилятора и оптимизации, есть исправление для программирования. Сохраните среду перед выделением вектора. Переместите setjmp в начало программы. В зависимости от использования вектора и логики ошибок в реальной программе могут потребоваться другие изменения.

изменить 21 января -------

мое обоснование (с использованием g ++ - mp-4.4 -Wextra -O1 main.cpp):

#include <setjmp.h>
#include <vector>
#include <iostream>

int main(int argc, char**) {
    jmp_buf env;
    int id = -1, idd = -2;

    if ((id=setjmp(env)))
        idd = 1;
    else 
        idd = 0;
    std::cout<<"Start with "<< id << " " << idd <<std::endl;
    std::vector<int> foo(argc );

    if(id != 4)
        longjmp(env, id+1);

    std::cout<<"End with "<< id << " " << idd <<std::endl;
}

Никаких предупреждений; а. произведено:

Начать с 0 0
Начать с 1 1
Начать с 2 1
Начать с 3 1
Начать с 4 1
Завершить с 4 1

person gary    schedule 20.01.2010
comment
longjmp над созданием объекта C ++ - это UB, и поэтому ваше предложение неверно. 18.7.4: Сигнатура функции longjmp (jmp_buf jbuf, int val) имеет более ограниченное поведение в этом международном стандарте. Если какие-либо автоматические объекты будут уничтожены сгенерированным исключением, передающим управление другой (целевой) точке в программе, то вызов longjmp (jbuf, val) в точке выброса, который передает управление той же (целевой) точке, имеет неопределенное поведение. . - person Tronic; 21.01.2010
comment
Код, который вы добавили в редактирование, вызывает утечку памяти: (valgrind) определенно потеряно: 16 байтов в 4 блоках - person Tronic; 22.01.2010