1

Можно ли сделать C++ безопаснее?

Конечно, я наверное, таким вопросом слишком на многое замахнулся. Пусть не "безопасным", но хотя бы чуть менее "опасным" в том, что касается работы с динамической памятью. Я считаю C++ "опасным" языком в этом отношении и думаю, что многие со мной согласятся. С какими основными проблемами встречается человек, пишущий на C++? Думаю, не сильно ошибусь, если скажу, что это утечки памяти и выход за границы диапазонов динамических массивов. С первой проблемой дело обстоит очень сложно, зато незначительные утечки памяти редко вызывают крах программы, если она не работает круглосуточно (например, некая игра для PC, текстовый процессор или какой-нибудь Computer-Aided Design). А вот выход за границы порой валит прогу в самом интересном месте. Даже если вы используете только safe arrays со всяческими проверками и генерацией исключений - все равно бывает, что не один час уйдет на поиск места, где произошла ошибка.
Признатся, я перешел с C++ на Java во многом именно из-за проблем с памятью. Но, кроме сборщика мусора, мне в Java импонирует еще одна вещь: в случае возникновения исключения во время исполнения программы, Java распечатывает стек вызовов! Устраняя глюки (те, что вызывают исключения) в программах на Java, я даже отладчик не использовал, и долгое время (первый год) не пользовался им вообще. После C++ это было что-то!!! Сообщения об ошибках в C++ которые выплевывались при генерации исключений и распечатка содержимого стека (одни цифири) не давали непосвященному вроде меня почти никакой информации о том, где произошла ошибка. Особенно, если использовались шаблоны, а ошибка происходила где-нибудь в debug/vector. В этой статейке я расскажу о своих попытках распечатки стека вызовов в C++ (в случае генерации исключения), в таком же виде, в каком я привык это видеть в Java. Специально для этой этого я написал маленькую программку (надеюсь, моя статья не такая паршивая, как эта программа :)))).

#include <stdio.h>
#include <vector>
void fillDoubleVector(vector<double> &vect){
vect.resize(20);
unsigned size = vect.size();
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*(double)size - size/(double)(i+1);
}
//-----------------------------------------------------
void fillLongVector(vector<long> &vect){
vector<double> dvect;
fillDoubleVector(dvect);
unsigned size = dvect.size();
vect.resize(size);
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*size - size/(i+1);
}
//-----------------------------------------------------
int main(int n, const char **args){
vector<long> lvect;
fillLongVector(lvect);
for(int i=0; i < lvect.size(); i++)
printf("lvect[%i] = %i\n", i, lvect[i]);
return 0;
}

Программу сохранил в файле test.cpp откомпилил в MinGW:

g++ -o test.exe -D _GLIBCXX_DEBUG -g3 test.cpp

Определил _GLIBCXX_DEBUG , что бы в программе использовалась отладочная версия STL (которая хотя бы сообщает о таких ошибках, как выход за пределы диапазона). Получил вот такое сообщение об ошибке:

C:/DEV-CPP/BIN/../lib/gcc/mingw32/3.4.2/../../../../include/c++/3.4.2/
debug/vector:192:
error: attempt to subscript container with out-of-bounds index 20,
but container only holds 20 elements.

Objects involved in the operation:
sequence "this" @ 0x0074FDC0 {
type = N15__gnu_debug_def6vectorIdSaIdEEE;
}
abnormal program termination
Замечательное и очень информативное сообщение об ошибке! Только местоположение ошибки указано весьма туманно. Было бы еще змечательно, что бы после этого еще и стек вызовов распечатывался. Переделал я эту программулину, вставив в начало и конец каждой функции блоки try-catch:

#include <stdio.h>
#include <exception>
using namespace std;
#include <vector>
//Класс-исключение:
class MyException : public exception
{
public:
MyException(const char *msg, int line)
{
printf("Error in file %s : %i\n", msg, line);
}
};
//--------------------------------------------------
void fillDoubleVector(vector<double> &vect){
try{
vect.resize(20);
unsigned size = vect.size();
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*(double)size - size/(double)(i+1);
}catch(exception ex){
throw MyException(__FILE__, __LINE__);
}
}
//-----------------------------------------------------
void fillLongVector(vector<long> &vect){
try{
vector<double> dvect;
fillDoubleVector(dvect);
unsigned size = dvect.size();
vect.resize(size);
for(int i=size-1; i >= 0; i++)//Ошибка
vect[i] = i*size - size/(i+1);
}catch(exception ex){
throw MyException( __FILE__, __LINE__);
}
}
int main(int n, const char **args){
try{
vector<long> lvect;
fillLongVector(lvect);
for(int i=0; i < lvect.size(); i++)
printf("lvect[%i] = %i\n", i, lvect.at(i));
}catch(exception ex){
printf("Error in file %s : %i\n", __FILE__, __LINE__);
}
return 0;
}

Откомпилил, запустил - млин, результат нисколько не изменился!!! Чертовщина, блин! Похоже, что в vector при выходе за пределы массива не выбрасывается исключение, во всяком случае в реализации MinGW. Полез в заголовочные файлы (исходников всей библиотеки, к сожалению, не было) - так и есть, не выбрасывается даже в отладочной версии.
Потом уже прочел в описании STL, что оператор индексирования [] исключение не выбрасывает и проверок никаких не делает, но зато есть функция-член at(), которая работает точно так же как оператор [], но проверки делает и исключение выбрасывает.
Отладочную версию файла vector я несколько раскурочил (перекрестившись и сохранив его копию), вставив в тело функции operator [] вызов функции at() и добился-таки желаемого:

Error in file test2.cpp : 23
Error in file test2.cpp : 36
Error in file test2.cpp : 47

Жаль, что старое информативное сообщение об ошибке уже не выводится. Но все можно исправить, для этого надо немного поковырять исходники библиотек(насколько я понимаю - вставить вместо вызова функции abort() генерацию исключения). Найти бы тока время, что бы этим заняться, и трафик, что бы их (stdlibc++ sources) скачать :)
Еще в дополнение замечу, что кроме выходов за границы есть и другие ошибки, которые можно было бы так отлавливать - главное, что бы при их возникновении выкидывалось исключение. Конечно, ручками вставлять кучу блоков try/catch вряд ли кто захочет, да и вряд ли это нужно делать ручками. Но вот если написать такую приблуду, которая будет сама это в исх. код вставлять, потом все это компилить, а потом из исходного кода удалять... Напишите для меня такую, кто-нибудь, заплатить не обещаю, но обещаю 100 раз скачать :)

1 коммент.:

Basil2 комментирует...

Посмотрите на STLport.
В debug-режиме эта версия STL умеет как раз то, что вы ищете.

Отправить комментарий