Предупреждение «может быть забит» на объекте C ++ с помощью setjmp - PullRequest
8 голосов
/ 08 января 2010
#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, которые требуют использовать его для обработки ошибок.

Ответы [ 4 ]

20 голосов
/ 21 января 2010

Правило состоит в том, что любая энергонезависимая, нестатическая локальная переменная в кадре стека, вызывающая 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);  
}

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

Редактировать

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

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

5 голосов
/ 08 января 2010

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

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

2 голосов
/ 08 января 2010

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

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

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

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

0 голосов
/ 21 января 2010

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

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

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

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

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

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

редактировать 1/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

...