Простой способ найти неинициализированные переменные-члены
Я ищу простой способ обнаружить неинициализированные переменные-члены класса.
Нахождение таких переменных как во время выполнения, так и на этапе компиляции подходит.
На данный момент я ставлю точку останова в конструкторе класса и проверяю переменные по одной.
5 ответ(ов)
Если вы используете GCC, вы можете применить флаг -Weffc++, который генерирует предупреждения, когда переменная не инициализирована в списке инициализации членов. Например:
class Foo
{
int v;
Foo() {}
};
Приведет к следующему предупреждению при компиляции:
$ g++ -c -Weffc++ foo.cpp -o foo.o
foo.cpp: In constructor ‘Foo::Foo()’:
foo.cpp:4: warning: ‘Foo::v’ should be initialized in the member initialization list
Однако есть и недостатки: флаг -Weffc++ также будет предупреждать вас, если переменная имеет корректный конструктор по умолчанию и инициализация не обязательна. Он также выдает предупреждения, когда вы инициализируете переменную в конструкторе, но не в списке инициализации членов. Дополнительно он дает предупреждения по ряду других стилей C++, таких как отсутствие конструкторов копирования, так что вам, возможно, придется немного почистить свой код, если вы хотите регулярно использовать -Weffc++.
Существует также ошибка, которая вызывает постоянное предупреждение при использовании анонимных объединений, и единственный способ обойти это — отключить предупреждение с помощью:
#pragma GCC diagnostic ignored "-Weffc++"
Несмотря на это, я нашел -Weffc++ невероятно полезным для выявления множества распространенных ошибок в C++.
Флаг компилятора -Wuninitialized используется для проверки использования неинициализированных переменных. Это означает, что компилятор будет предупреждать вас, если переменная может быть использована до того, как ей будет присвоено какое-либо значение.
В приведенном вами примере кода:
struct Q {
int x, y;
Q() : x(2) {}
int get_xy() const { return x * y; }
};
Компилятор g++ не выдаст предупреждение о неинициализированной переменной y, если вы вызовете метод get_xy(), поскольку y не инициализирован, и вы собираетесь использовать его в выражении return x * y;. Предупреждение появится только в том случае, если вы вызовете метод get_xy() без присвоения значения переменной y.
Таким образом, флаг -Wuninitialized полезен для выявления потенциальных ошибок в коде, связанных с использованием переменных, значения которых не были определены. Однако, стоит отметить, что он может не обнаружить все возможные случаи, особенно в сложных конструкциях, где использование переменных может зависеть от логики программы.
Если вы используете Visual Studio, вы можете скомпилировать программу в режиме отладки, остановить ее в отладчике и проверить, какие переменные инициализированы байтами, содержащими 0xCC (стек) или 0xCD (куча).
Тем не менее, я бы на вашем месте рассматривал возможность использования инструмента статического анализа для более тщательного подхода.
В Visual Studio (MSVC) есть опция компилятора /sdl (включить дополнительные проверки безопасности) (http://msdn.microsoft.com/en-us/library/jj161081.aspx). Эта опция в режиме выполнения:
- Выполняет инициализацию членов класса. Автоматически инициализирует члены класса типа указателя в ноль при создании объекта (до выполнения конструктора). Это помогает предотвратить использование неинициализированных данных, связанных с членами класса, которые конструктор не инициализирует явно.
Следует отметить, что эта опция не поможет вам обнаружить неинициализированные переменные-члены на этапе компиляции, но делает поведение программы более предсказуемым, когда это происходит в режиме выполнения. Тем не менее, не стоит рассчитывать на то, что эта опция будет включена в коде, который вы пишете.
Будьте осторожны! Компиляторские опции, предложенные здесь, не являются ни надежными, ни независимыми от версии. Рассмотрим простой пример:
class A {
int a;
public:
void mA() {
printf("haha");
++a;
int g = 2/a;
printf("%i\n",g);
}
};
int main() {
A a;
a.mA();
}
При компиляции с g++ -O3 -Weffc++ -Wuninitialized данное код вызывает предупреждение о "неинициализированной" переменной на версиях gcc до 4.6 включительно, тогда как на 4.7 и 4.8 (тестировалось на MacPorts) он проходит без проблем. Интересно, что если мы уберем printf("haha");, то версии 4.7 и 4.8 вновь начинают видеть "неинициализированную" переменную A::a. Clang немного лучше в этом плане, так как он как-то присваивает мусор (вместо удобного 0) неинициализированным переменным, что позволяет быстрее и проще заметить их катастрофические последствия.
Также мне не удалось обнаружить вышеупомянутую неинициализированную переменную A::a с помощью valgrind; может, уважаемые коллеги, предлагающие valgrind, могут предоставить подходящие опции для обнаружения этой ошибки.
В итоге: отличный вопрос, но в данный момент надежных решений не так уж много... (как я это вижу).
Имеют ли круглые скобки после имени типа значение при использовании new?
Каковы преимущества инициализации списка (с использованием фигурных скобок)?
Как проще всего инициализировать std::vector с жестко заданными элементами?
Как инициализировать приватные статические члены данных в заголовочном файле
Как инициализировать статическую Map?