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

#include 
#include 

int main (int argc, char **) {
 std:: vector  foo (argc);
 jmp_buf env;
 if (setjmp (env)) возвращает 1;
}

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

/usr/include/c++/4.4/bits/stl_vector.h: в функции 'int main(int, char**)':
/usr/include/c++/4.4/bits/stl_vector.h:1035: предупреждение: переменная ' __first 'может быть забит' longjmp 'или' vfork ' 

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

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

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

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

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 макрос, который не имеет volatile-квалифицированного типа и был изменен между вызовом setjmp и вызовом longjmp, является неопределенным.

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

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

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

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

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

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

Быстрый ответ: сбросьте флаг -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

Другие вопросы по тегам