Интересный глюк или различие операторов сравнения и присваивания

Вот накнулись на ситуацию, типичную для начинающего программиста. Человек написал класс для получения строк из файла. Приводить интерфейсную часть не буду, только покажу кусочек реализации одного из методов:


bool stringProvider::next(string &store) {
mutex.lock();
bool res = getline(in,store);
mutex.unlock();

return res;
}

ничего особенного в коде этом нет, все достаточно просто – происходит чтение строки из файла. Все переменные использованные здесь являются членами класса(private). Сам код разбирать не буду, скажу только что написан он для многопоточной программы и обьект существует в одном экземпляре, каждому из потоков передается указатель на него. Для того, чтобы потоки не подрались и не прочитали из файл кто знает что использован mutex(MUTual EXclusive object – взаимоисклющая блокировка), подробно о ней можно почитать на страницах интернета.

Так вот, после запуска программы на инструкции mutex.lock() происходил… Segmentation fault. Исследование кода показало, что с самим классом все хорошо, метод написан вполне корректно. Более того, после закоментирования операций с мьютексом та же проблема возникает на getline! Прям мистика какая-то.

После детального перелопачивания кода был найден интересный классический глюк. В одном из методов класса клиента данного обьекта был такой код:

if ( provider = 0) return false;

provider – это и есть указатель на наш обьект провайдер. Автор просто хотел проверить указатель на валидность, но вместо оператора сравнения(==) написал оператор присваивания. Поскольку все операторы возвращают значение, и = не исключение(возвращаемое значение равно значению переменной после присваивания), то данный код эквивалентен записи


provider = 0;
if (0) return false;

условие не выполнится никогда, а указатель после этого указывает на нулевой адрес. Почему же тогда метод вызвался, если стоит p->next(url)? Поидее слет должен произойти именно здесь, так как разыменовывается указатель на ноль. На самом деле все обстоит немножко не так.

В Си++ метод – это обычная функция, только кроме явных параметров в нее еще и передается указатель на тот обьект, который ее вызывает. Тоесть вызов p->next(url) на самом деле транслируется в next(p,url).
И все хорошо до тех пор, пока Вы не обращаетесь к данным-членам класса – вот в этом месте и просходит разыменовывание и программа аккуратно падает на дно. А в месте с ней и автор погружается в долгие поиски глюков.

Так что отличайте присваивание от сравнения – избежите множества странных(но только на первый взгляд) глюков.

P.S. В каком то из компиляторов(из старых) была очень полезная фича – если в операторе сравнения встречалось присваивание, сразу же выскакивало предупреждение: “Здесь стоит присваивание. Вы уверены что нужно не сравнение?”. В gcc 4.0.1 которым мы пользуемся сейчас, к сожалению, такой полезной фичи нет. А жаль…



Digital Ocean
Провайдер облачного хостинга - заведи свой виртуальный сервер всего за $5 в месяц !

One Comment

  1. Mikhail Gusarov

    Как это нет? А ключик -Wall на что?

    [mag@vertex:~]% g++-4.0 -o a -Wall a.cpp
    a.cpp: In function ‘int main()’:
    a.cpp:3: warning: suggest parentheses around assignment used as truth value

Leave a Reply