Comments 82
www.govnokod.ru — покопайтесь там
Там я был. Прикольный сайт. Но это не то. Кстати рекомендую посетить. Многое порадует и посмешит.
А многое — ужаснет
Все мы смеемся, а часто ужасаемся над теми приимерами кода, которые есть на подобных сайтах. Все мы говорим: «Уж я то так точно не напишу». Откуда же тогда столько «этого самого» кода везде? :-)
Есть замечательная русская пословица — «в чужом глазу соломину видеть, в своём — бревна не замечать». Как-то так. Свое «Я» критиковать далеко не всем с руки. Куда уж как проще бугогашить над чужими ошибочками, не замечая своих ошибищ.
А иногда, в условиях нехватки времени, осознанно пишешь говнокод и обещаешь себе после релиза его убрать. Релиз проходит, а код остаётся и оставаться он так может очень долго.
си такой си!
Свежий GCC ругается, но так как этот пример у меня лежит уже давно, видимо было время когда варнинга не было.
42 is what you get when you multiply 6 by 9.
Классика, алиасинг.
Спойлер внизу.
#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. Пример весьма специфичен на мой взгляд, в том смысле не понятно, можно ли диагностировать его автоматически. Возможно так было задумано специально…
Пример 1. Visual C++ тоже недоволен.
Пример 2. Интересная беда, но как подступиться к диагностике (пока) не знаю.
Пример 3. Проблема с алиасингом тоже интересна. Сложно. Записал «на подумать».
Пример 4. Пример весьма специфичен на мой взгляд, в том смысле не понятно, можно ли диагностировать его автоматически. Возможно так было задумано специально…
Мой самый страшный кошмар — дважды сделать delete по одному и тому же адресу.
Когда-то на втором курсе писал миниатюрную библиотечку для отрисовки псевдоокошек в консоли, с полями ввода, списками и т.д. Получилось так, что окошко в деструкторе делало 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);
//…
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);
//…
У нас старая среда (не знаю на сколько это актуально в новых), последними ошибками в которые долго искали были:
1. Выход за границы массива — std::vector, std::set и динамические по new []
2. Неинициализированные переменные члены класса, стояло банальное if ( m_iCount == 0 )…
3. Многопоточность — вызов метода который менял данные не защищенные критической секцией
Искали долго потому что падало в другом месте или работало по рандому.
Еще было бы классно чтобы компилятор мог анализировать printf — хватает или нет переменных, все ли они того самого типа и неявных преобразований не происходит и пр.
А приведенную банальщину с запятой, вызовом не того метода и прочее легко обнаружить в дебаггере.
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 или нет. Причины раздумий и нерешительности:
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 дает замечательную возможность прикрутить подобную (printf/scanf) проверку к любой функции.
Например, предположим, вы решили сделать свою функцию форматного логирования, и хотите чтобы компилятор ругался если она будет вызвана неправильно — достаточно просто добавить аттрибут:
Все! Компилятор будет ругаться если кто-то напишет:
Компилятор 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
Из-за ошибки типа «запятая вместо точки» американцы однажды потеряли дорогой спутник.
Так что опечатка может стоить дорого, очень дорого.
Так что опечатка может стоить дорого, очень дорого.
#define TRUE FALSE //Счастливой отладки :)
Вики:
en.wikipedia.org/wiki/Gotcha_(programming)
Классика жанра:
freeworld.thc.org/root/phun/unmaintain.html
en.wikipedia.org/wiki/Gotcha_(programming)
Классика жанра:
freeworld.thc.org/root/phun/unmaintain.html
Несколько раз палился на примерно таком коде. Правда не в продакшене, а на олимпиадах, когда времени в обрез и можно писать грязный код.
for(int i=0;i<n;i++) {
for(int j=0;j<n;i++) {
// do something
}
}
Классика:
if( a = 5 )
{
…
}
if( a = 5 )
{
…
}
Не интересно. Про это скажет и компилятор:
warning C4706: assignment within conditional expression
Хочется увидеть те примеры, которые на ловятся компилятором.
warning C4706: assignment within conditional expression
Хочется увидеть те примеры, которые на ловятся компилятором.
Как насчёт:
?
SomeClass* obj = new SomeClass();
// ...
if ( obj ) {
// do something
}
?
Не понял проблемы. Код, как код. :) Прошу пояснить мысль.
Во-первых, если указатель где-то был проинициализирован как NULL, и NULL при этом не равняется 0 (а такое возможно, т.к. в стандарте не указано, что обязательно должен быть 0), то получаем ой.
Во-вторых, могут вылезти восхитительные глюки на 64-битных платформах (опять же, на некоторых компиляторах), когда 64-битный указатель даункастится до 32-битного инта, который потом сравнивается с нулём. В итоге не нуль может оказаться нулём.
Во-вторых, могут вылезти восхитительные глюки на 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 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 выдаем предупреждение.
1. находим многооператорные макросы (содержат в теле «;» и не содержат обрамляющих {} или do {… } while(0))
2. на все конструции if/for/while (...) MULTILINE_MACRO выдаем предупреждение.
Это все только со стороны так просто кажется. Меня прям передергивает, когда кто-то что-то собирается в Си++ регулярными выражениями ловить. Бросьте эти глупости. :)
Конечно строится дерево. И много еще чего делается. Беда с препроцессором. Сейчас мы используем препроцессор от Visual C++. Соответственно туда внутрь не влезешь. Путь использовать препроцессор из boost тоже не прост.
1) Он глючит при работе с заголовочными файлами от Visual Studio. По крайней мере автор никак не исправит одну важную критичную для нас ошибку. Занят оплачиваемой работой. Почему сами не поправим — см. пункт 2.
2) Что-то поправить и расширить в реализации препроцессора от boost для сбора информации — задача для титанов. Кто хочет — можете посмотреть исходные коды. :)
Конечно строится дерево. И много еще чего делается. Беда с препроцессором. Сейчас мы используем препроцессор от Visual C++. Соответственно туда внутрь не влезешь. Путь использовать препроцессор из boost тоже не прост.
1) Он глючит при работе с заголовочными файлами от Visual Studio. По крайней мере автор никак не исправит одну важную критичную для нас ошибку. Занят оплачиваемой работой. Почему сами не поправим — см. пункт 2.
2) Что-то поправить и расширить в реализации препроцессора от boost для сбора информации — задача для титанов. Кто хочет — можете посмотреть исходные коды. :)
подскажите, пожалуйста, из какого фильма фотография черного зайца?
Почему-то многие Си-программисты забывают (не знают?) про ключевое слово volatile. Особенно этим грешат те, кто привык к ассемблеру.
Если переменная может изменяться из прерывания, то её надо обязательно объявлять как 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
}
for (int i=0; i < n; i++);
{
// do something
}
int f(int var, int other_var);
f(i, i++);
f(i, i++);
а для чего вам это нужно? хотите написать модуль к gcc?
www.angelikalanger.com/Conferences/Slides/CppInvalidIterators-DevConnections-2002.pdf
или это сложно для статистического анализа?
или это сложно для статистического анализа?
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
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(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++)
{
//…
}
for(unsigned char i=0; i<256; i++)
{
//…
}
помню, как-то долго писал на джаве, а потом на плюсах написал подобный код:
и долго не мог понять, в чем соль
for (char ch = 0; ch < 256; ch++) {
//something here
}
и долго не мог понять, в чем соль
классика жанра в t-sql
Пропущена запятая после Id, стало быть селектятся все идентификаторы, но колонка называется Name.
Почему-то иногда бывает весьма сложно определить такого рода косяк, особенно в сложных запросах.
select Id Name from dbo.Data where Id = 5;
Пропущена запятая после Id, стало быть селектятся все идентификаторы, но колонка называется Name.
Почему-то иногда бывает весьма сложно определить такого рода косяк, особенно в сложных запросах.
Я на си очень давно не писал уже, вот что сходу вспомнилось:
При запуске вы ведет EIGHT :)
#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. варнингов не было.
Можно еще попробовать ловить случаи когда операции с контейнером приводят к невалидности сохраненных итераторов.
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.
Собираю страшненькое от программистов