утечка мозга КРИС КАСПЕРСКИ АКА МЫЩЪХ Спецвыпуск: Хакер, номер #071, стр. 071-020-2 Простейшая тестовая программа все это наглядно подтверждает: программа, демонстрирующая невозможность определения размера блока по указателю // функция, принимающая указатель // и пытающаяся определить размер соответствующего ему блока foo(char *p){printf("sizeof says = %Xh\n_msize says = %Xh\n",sizeof(p),_msize(p));} main() { char buf[0x666]; char *p = malloc(0x999); // передаем указатель на начало блока foo(buf);foo(p); // передаем указатель на середину блока foo(&buf[6]);foo(p+9); } После запуска мы получим следующие весьма неутешительные данные: результат работы программы, определяющий размер блока по указателю // указатель на начало стекового буфера sizeof says = 4h, _msize says = 12E8CEh // sizeof и _msize провалились // указатель на начало динамического буфера sizeof says = 4h, _msize says = 9A0h // sizeof провалилась, _msize - почти ОК // указатель на середину стекового буфера sizeof says = 4h, _msize says = FFFFFFFFh // sizeof и _msize провалились // указатель на середину динамического буфера sizeof says = 4h, _msize says = D200h // sizeof и _msize провалились Мы видим, что _msize ведет себя очень странно и когда не может определить размер блока, возвращает какой-то мусор, никак не сигнализируя об ошибке. Поэтому выполнять контроль должна вызывающая функция, передавая вызываемой размер буфера как аргумент. Отсюда и появились char *fgets(char *string, int n, FILE *stream); char *strncpy(char *strDest, const char *strSource, size_t count) и другие подобные функции. Теоретически, они на 100% застрахованы от переполнения, но вот практически... значение n приходится рассчитывать вручную, а, значит, существует риск ошибиться! К тому же, если длина строки превышает n, в буфер копируется лишь «огрызок», что само по себе является нехилым источником проблем и вторичных ошибок. Приходится навешивать специальный обработчик, выделяющий дополнительную память и считывающий оставшийся «хвост», что значительно усложняет реализацию, делает код более громоздким и менее наглядным. Обычно стараются выбрать n так, чтобы его значение превышало размер наибольшей строки, выделяя память с запасом и не обращая внимания на то, что большинство строк использует лишь малую часть буфера... Нет! Память лучше всего выделять динамически, по мере возникновения в ней потребности! В идеале, строка должна представлять собой список (желательно двухсвязный), что не только ускорит операции удаления и вставки подстрок, но и ликвидирует проблему переполнения. О контроле границ заботиться уже необязательно, поскольку память под новые символы выделяется автоматически. В простейшем случае каждый элемент списка выглядит приблизительно так: struct slist { unsigned char c; struct slist *prev; |