Pull to refresh

Comments 82

Там я был. Прикольный сайт. Но это не то. Кстати рекомендую посетить. Многое порадует и посмешит.
А многое — ужаснет
Все мы смеемся, а часто ужасаемся над теми приимерами кода, которые есть на подобных сайтах. Все мы говорим: «Уж я то так точно не напишу». Откуда же тогда столько «этого самого» кода везде? :-)
Есть замечательная русская пословица — «в чужом глазу соломину видеть, в своём — бревна не замечать». Как-то так. Свое «Я» критиковать далеко не всем с руки. Куда уж как проще бугогашить над чужими ошибочками, не замечая своих ошибищ.
О чем и речь — мы все (программисты) «выше среднего», мы никогда не допускаем глупых ошибок. Только откуда же столько «того» кода :-).
А иногда, в условиях нехватки времени, осознанно пишешь говнокод и обещаешь себе после релиза его убрать. Релиз проходит, а код остаётся и оставаться он так может очень долго.
А потом он внезапно кусает тебя :)
С++ такой С++, если быть точнее
Свежий GCC ругается, но так как этот пример у меня лежит уже давно, видимо было время когда варнинга не было.
#include <stdio.h>

int main(int argc, char *argv[])
{
  int foo = -1;
  int bar = 0;

  if (foo < 0 & & bar == 0)
  {
    printf("true\n");
  }
  else
  {
    printf("false\n");
  }
  return 0;
}


42 is what you get when you multiply 6 by 9.
#include <stdio.h>

#define SIX 1+5
#define NINE 8+1

int main(int argc, char* argv[])
{
  int value = SIX * NINE;
  printf("Answer = %d\n", value);
  return 0;
}


Классика, алиасинг.
/*
 * Works as intended on little-endian only! (I think)
 *
 * $ gcc file.c
 * $ ./a.out
 * 1. v=10
 * 2. v=11
 * 3. v=11
 *
 * $ gcc -O2 file.c
 * $ ./a.out
 * 1. v=10
 * 2. v=10
 * 3. v=11
 */

#include <stdio.h>

void f(int *i, long *l)
{
  printf("1. v=%ld\n", *l);
  *i = 11;
  printf("2. v=%ld\n", *l);
}

int main()
{
  long a = 10;
  f((int *) &a, &a);
  printf("3. v=%ld\n", a);
  return 0;
}


Спойлер внизу.
/*
 * Explain why both calls f(d) and f(&d) compile.
 *
 * f(&d) shouldn't compile because f() takes a reference to derived,
 * it doesn't take a pointer.
 *
 * How to fix this?  That is, how to change the program so that f(&d) would not
 * compile?
 */

#include <iostream>

class base
{
  public:
    base(int i): i(i) {}

    int get_i() const { return i; }

  protected:
    int i;
};

class derived: public base
{
  public:
    derived():
      base(0)
    {}

    derived(const base *b):
      base(b->get_i())
    {}

    derived(const derived &d):
      base(d.i)
    {}

    void say_hello() const { std::cout << "hello" << std::endl; }
};

void f(const derived &d)
{
  d.say_hello();
}

int main()
{
  derived d;
  f(d);  // compiles, as expected
  f(&d); // why does this line compile?
}

/*
 * Implicit type conversion.  &d is "dervied *" but it also is "base *".
 * "base *" is passed to derived(const base *b) constructor and a temporary
 * derived object is created, which is then passed to f().
 */

Спасибо!
Пример 1. Visual C++ тоже недоволен.
Пример 2. Интересная беда, но как подступиться к диагностике (пока) не знаю.
Пример 3. Проблема с алиасингом тоже интересна. Сложно. Записал «на подумать».
Пример 4. Пример весьма специфичен на мой взгляд, в том смысле не понятно, можно ли диагностировать его автоматически. Возможно так было задумано специально…
на 2:
дефайны выражений по дефолту надо предлагать брать в скобки. Хотя чем дальше в этот лес тем больше граблей.
Мой самый страшный кошмар — дважды сделать delete по одному и тому же адресу.

Когда-то на втором курсе писал миниатюрную библиотечку для отрисовки псевдоокошек в консоли, с полями ввода, списками и т.д. Получилось так, что окошко в деструкторе делало delete для всех добавленных на него элементов, а я забыл про это и удалял их еще раз вручную. Самое интересное, что сразу ничего не происходит, а мистика начинается дальше по мере работы: особенно меня порадовало, когда форма с единственным полем для ввода падала при попытке создать чекбокс :)

Отловить такой баг достаточно трудно. У меня на это ушли примерно сутки.
можно после удаления переменной присвоить NULL, delete NULL ничего не удаляет и не вызывает ошибки.
Код, если в ОЧЕНЬ упрощенном виде, примерно такой:

class manager
{
public:
list items;

~manager()
{
for(int idx=0; idx < items.count(); idx++)
delete items.get(idx);
}
};

manager* mgr = new manager();
control *edit = new editbox();
control *btm = new button();
mgr->items.add(edit);
mgr->items.add(button);

// .....

delete edit;
delete button;
delete mgr;


Так что присвоить там некуда :(
Красиво. Жаль, но видимо здесь статический анализ бессилен. Слишком сложно.
Я бы просто посоветовал отказываться от такого стиля программирования, сам от него очень сильно страдал. Сейчас использую либо boost::shared_ptr если это реально надо, либо пишу свои классы с полноценной поддержкой копирования, либо использую свой аналог boost::shared_ptr, в этом случае код бы принял вид.

class manager
{
public:
list items;

~manager()
{
for(int idx=0; idx < items.count(); idx++)
delete items.get(idx);
}
};

boost::shared_ptr mgr(new manager());
boost::shared_ptr edit(new editbox());
boost::shared_ptr btn(new button());
mgr->items.add(edit);
mgr->items.add(button);

//…
Ой, удаление в деструкторе не надо)
Чаще всего даже shared_ptr не нужен. Достаточно boost::scoped_ptr или std::auto_ptr (если надо передавать права владения).
Особенно радует, что при возникновении исключения все грамотно отработает и память не потечет.
У нас старая среда (не знаю на сколько это актуально в новых), последними ошибками в которые долго искали были:
1. Выход за границы массива — std::vector, std::set и динамические по new []
2. Неинициализированные переменные члены класса, стояло банальное if ( m_iCount == 0 )…
3. Многопоточность — вызов метода который менял данные не защищенные критической секцией
Искали долго потому что падало в другом месте или работало по рандому.
Еще было бы классно чтобы компилятор мог анализировать printf — хватает или нет переменных, все ли они того самого типа и неявных преобразований не происходит и пр.

А приведенную банальщину с запятой, вызовом не того метода и прочее легко обнаружить в дебаггере.
Еще было бы классно чтобы компилятор мог анализировать printf — хватает или нет переменных, все ли они того самого типа и неявных преобразований не происходит и пр.

Хочу консультация. Я вот не знаю, делать анализ строки формата и аргументов для функций типа printf или нет. Причины раздумий и нерешительности:
1) А так ли уже в современных программах нужны функции printf? Что-то тестовое распечатать, программу для курсовой написать — да, нужны. А в настоящем проекте ведь все равно все запрятано в ресурсы, выводится через нормальный boost::format и так далее. Или я не прав?
2) Это умеет делать gcc и многие другие компиляторы. Есть подозрение, что это может появиться и в Visual C++. Правда не аргумент. Ибо включая VS 2010 этой диагностики нет.
3) Основная причина. Эти диагностики есть в Code Analysis for C/C++ входящий в состав Visual Studio 2010 Premium/Ultimate.

Вопрос. Делать или нет? Есть желающие получить такие проверки для printf, sprintf и т.д.?
Я имел ввиду все функции которые имеют вид printf. В нашем случае мы используем CString::Format (из MFC) + наш самописный класс с аналогичной функцией (грубо говоря CNashMegaString::Format), который вызывает в итоге как и CString функцию _vstprintf + кое где есть wsprintf

На самом деле этот пункт самый не критичный из всех мною перечисленных ;)
Intellij IDEA в python коде правильно определяет нехватку параметров в выражении: «abc %s def %s» % («z», «w»)
Так что это вполне реально сделать.
Вроде бы в gcc такая проверка уже есть.
В gcc есть. В Visual Studio .../2005/2008/2010 нет. Есть только в редакциях типа Premium (подсистема Code Analysis for C/C++). Вот и вопрос — сделать в PVS-Studio или нет?
Информация «программеру на заметку»:

Компилятор gcc дает замечательную возможность прикрутить подобную (printf/scanf) проверку к любой функции.
Например, предположим, вы решили сделать свою функцию форматного логирования, и хотите чтобы компилятор ругался если она будет вызвана неправильно — достаточно просто добавить аттрибут:
int log_printf(int log_lev,log_file_t *lf,const char*fmt,...)
__attribute__((format(printf,3,4))); // 3 это позиция форматной строки, а 4 это "..."


Все! Компилятор будет ругаться если кто-то напишет:
log_printf(LOG_DEBUG,log_file,"data=%s\n",1);


UFO just landed and posted this here
Несколько раз палился на примерно таком коде. Правда не в продакшене, а на олимпиадах, когда времени в обрез и можно писать грязный код.
for(int i=0;i<n;i++) {
  for(int j=0;j<n;i++) {
    // do something
  }
}
Уже умеем находить в PVS-Studio. Такое бывает и в обыкновенном коде.
Не интересно. Про это скажет и компилятор:
warning C4706: assignment within conditional expression
Хочется увидеть те примеры, которые на ловятся компилятором.
Как насчёт:

SomeClass* obj = new SomeClass();
// ...
if ( obj ) {
    // do something
}

?
Не понял проблемы. Код, как код. :) Прошу пояснить мысль.
Извините, промахнулся с ответом. См. ниже.
Во-первых, если указатель где-то был проинициализирован как NULL, и NULL при этом не равняется 0 (а такое возможно, т.к. в стандарте не указано, что обязательно должен быть 0), то получаем ой.

Во-вторых, могут вылезти восхитительные глюки на 64-битных платформах (опять же, на некоторых компиляторах), когда 64-битный указатель даункастится до 32-битного инта, который потом сравнивается с нулём. В итоге не нуль может оказаться нулём.
Верю, что где то есть особенные компиляторы и архитектуры, где nullptr это не 0. Но что-то это экзотичное. В любом коде огромное количество конструкций вида:

if (ptr)
  ptr->Foo();


И как следствие никак нельзя предупреждать про это. Реализация предлагаемой диагностики разумна только в тех самым экзотических компиляторах.
Этот баг есть когда ставим breakpoint с условием. Если по случайности написать a = 5, то VS будет присваивать значение 5, вместо того, чтобы остановится при a == 5, но вот нигде об этом не предупредит.
А еще однажды был такой баг: многострочный дефайн в духе
#define FUNC(X) Something1 \
Something2

а потом вот такой if:

if( a == 0 )
FUNC(X);

Гадость в том, что if без фигурных скобок и, соответственно Something1 выполниться только при условии ( a == 0 ), а Something2 — всегда.
Вот это пример интересный. Спасибо. Правда как его ловить (пока) не знаю.
Нужно себя приучить всегда писать многострочные define'ы так:
#define FUNC(X)\
do{\
statement;\
...\
statement;\
}while(0)


и можно навсегда забыть где нужны фигурные скобки :) приём кстати очень распространённый. Вроде бы даже поддерживается компиляторами. Увы, источник уже не найду
Как исправить понятно. Не понятно, как диагностировать. Предлагать во всех случаях объединять многострочный макрос в блок не пойдет. Будет слишком много глупых и ложных срабатываний. Люди иногда большие фрагменты программы на макросах пишут… Там такое…
Если многострочный макрос вставляется после if/while, тело которых не заключено в {}. То есть предлагать объединять только те многооператорные макросы, которые используются в подозрительных конструкциях.
Тут сложность в том, что анализ нужен на этапе препроцессирования. А это не просто. Мало того, что нужно влезть внутрь препроцессора, так и еще на том уровне уже понимать, что if это if.
А разве статический анализатор не занимается построением синтаксического дерева, на основе которого и занимается анализом? Вообще выглядит так, словно это можно чуть ли не регулярным выражением поймать:

1. находим многооператорные макросы (содержат в теле «;» и не содержат обрамляющих {} или do {… } while(0))

2. на все конструции if/for/while (...) MULTILINE_MACRO выдаем предупреждение.
Это все только со стороны так просто кажется. Меня прям передергивает, когда кто-то что-то собирается в Си++ регулярными выражениями ловить. Бросьте эти глупости. :)

Конечно строится дерево. И много еще чего делается. Беда с препроцессором. Сейчас мы используем препроцессор от Visual C++. Соответственно туда внутрь не влезешь. Путь использовать препроцессор из boost тоже не прост.

1) Он глючит при работе с заголовочными файлами от Visual Studio. По крайней мере автор никак не исправит одну важную критичную для нас ошибку. Занят оплачиваемой работой. Почему сами не поправим — см. пункт 2.

2) Что-то поправить и расширить в реализации препроцессора от boost для сбора информации — задача для титанов. Кто хочет — можете посмотреть исходные коды. :)

То есть вы работаете с кодом, предварительно обработанным препроцессором? Как же тогда вообще можно отлавливать какие-то ошибки, связанные с использованием #define?
подскажите, пожалуйста, из какого фильма фотография черного зайца?
Почему-то многие Си-программисты забывают (не знают?) про ключевое слово volatile. Особенно этим грешат те, кто привык к ассемблеру.
Если переменная может изменяться из прерывания, то её надо обязательно объявлять как volatile.
Иначе код типа:
int t = 0;
...
while( !t ) 
{    
}

При оптимизации заменится на бесконечный цикл.
Да, именно эта история и вдохновила меня на этот комментарий :)
даже это не всегда спасает, писалось тут, так что возможны опечатки

class some_one
{
public:
    some_one():
        m_run( true )
    {
    }
    void thread_run()
    {
        /* v1 */
        while ( m_run ) // <-- впадает в бесконечный цикл в релизе MSVC 7.1 и MSVC 8.0
        {
        }
        /* v2 */
        while ( true )
        {
            if ( !running ) // <-- а вот так работает как задумано
                break;
        }
    }
    bool running()
    {
        return m_run;
    }
    void stop()
    {
        m_run = false;
    }
private:
    volatile bool m_run;
}


А вы бы посмотрели страшненькое из инженерно-строительного дела! Тогда квартиру в новострое покупать не захочется :D
Как-то в стиле К.О. :)
А те кто колбасу делает, тот её часто есть избегает. Таких примеров можно массу найти. :)
Вот такое много раз видел и писал :)

for (int i=0; i < n; i++);
{
// do something
}
Классика жанра. Мы в PVS-Studio уже умеем искать подобное. Например, только что рассматривал найденную им ошибку в Fennec Player:
for(i=0; i<16; i++); 
  for(j=0; j<32; j++) 
  { 
    settings.conversion.equalizer_bands.boost[i][j] = 0.0; 
    settings.conversion.equalizer_bands.preamp[i]   = 0.0; 
  }
а для чего вам это нужно? хотите написать модуль к gcc?
Думаю кое что вполне реализуемо. Большое спасибо за эту ссылку!
int i = 2;
string s1 = "abc" + i;             //  "c"
string s2 = (string) "abc" + i;    //  "abc2"
Delphi:

var
P: PChar;
begin
P := PChar('XY');
OutputDebugString(P); // XY
P := PChar('X');
OutputDebugString(P); // ловим AV

во втором случае 'X' компилятором определяется как Char и его код присваивается указателю — ловим AV
void func(char **names, int count)
{
for(int i=0; i<count; i++) printf("%s\n", names[i]);
}


char names[32][32], *names_p[32];
for(int i=0; i<32; i++)
{
sprintf(names[i], "%d", i);
names_p[i] = names[i];
}
func(names, 32); // упадет
func(names_p, 32); // не упадет
еще классика
for(unsigned char i=0; i<256; i++)
{
//…
}
к счастью о таком компиляторы варнинг сыпят, по крайней мере (у меня gcc 3.3)
Я чаще эту ошибку наоборот встречаю:

for (size_t i = n; i >= 0; --i)
{

}

Особенно при обходе массива с конца :)
помню, как-то долго писал на джаве, а потом на плюсах написал подобный код:
 for (char ch = 0; ch < 256; ch++) {
   //something here
}


и долго не мог понять, в чем соль
классика жанра в t-sql

select Id Name from dbo.Data where Id = 5;


Пропущена запятая после Id, стало быть селектятся все идентификаторы, но колонка называется Name.

Почему-то иногда бывает весьма сложно определить такого рода косяк, особенно в сложных запросах.
Я на си очень давно не писал уже, вот что сходу вспомнилось:

#include <stdio.h>

const char* DICT[] = {
	"ZERO",
	"ONE",
	"TWO",
	"THREE",
	"FOUR",
	"FIVE",
	"SIX"
	"SEVEN",
	"EIGHT",
	"NINE"
};

int main(int arc, char** argv) {

	printf("%s\n", DICT[7]);

	return 0;
}



При запуске вы ведет EIGHT :)

some_func(*(new TinyXmlNode(...)));

как правило это явная ошибка утечки памяти.
под gcc не проверял, проект был под VS2005. варнингов не было.

Можно еще попробовать ловить случаи когда операции с контейнером приводят к невалидности сохраненных итераторов.
В тему про массив производных классов и указатель на базовый класс.
Указатель на производный класс можно привести к указателю на базовый класс, но указатель на указатель на производный класс нельзя приводить к указателю на указатель на базовый класс :))
Что должно получится на выходе?

typedef std::basic_stringstream<char> StringStream;

int main()
{ 
 StringStream ss;
 ss<<"(";
 for (int i = 0; i < 10; ++i)
  ss << "'" << i << (i != 9) ? "'," : "');";
 ss << "\0";
 std::cout << ss.str();
 return 0;
}


прошу прощения за некропостинг :)
Sign up to leave a comment.