Надо сказать , что в последнее время С не стоит на месте ,
и авторы новейших юниксовых компиляторов предприняли кое-какие шаги для того ,
чтобы предупреждать простых смертных о возможных потенциальных ошибках в коде .
- Незаконченные комментарии
a=b; /* this is a bug
c=d; /* c=d will never happen */
- Неправильная инициализация
if(a=b) c; /* a всегда равно b, но c верно и при if b!=0 */
Здесь легко попасть впросак , написав вместо 2-х знаков равенства один ,
и С ничем не сможет вам тут помочь
: (a=b) это совсем не булевское выражение!
Или рассмотрим такую конструкцию:
if( 0 < a < 5) c; /* this "boolean" is always true! */
Тут всегда true , потому что (0<a) - это либо 0 либо 1 , что естественно меньше 5.
Или:
if( a =! b)
c; /* будет скомпилировано как (a = !b), т.е. присваивание, и совсем не (a != b) или (a == !b) */
- Неправильный macros
#define assign(a,b) a=(char)b
assign(x,y>>8)
превращается в
x=(char)y>>8 /* совсем не то чего мы хотим */
- Несоответствие хидеров
Пусть foo.h включает:
struct foo { BOOL a};
файл F1.c включает
#define BOOL char
#include "foo.h"
файл F2.c включает
#define BOOL int
#include "foo.h"
В файлах F1 и F2 налицо противоречие при компиляции .
- Неопределенное возвращаемое значение
Ну и теперь предположим мы написали
int foo (a)
{ if (a) return(1); }
/* баг - при некоторых условиях функция может вообще ничего не вернуть */
Компилятор не скажет вам , где ошибка . И можете получить например зацикливание .
Или более того - представьте что эта функция вернет указатель !
- Непредсказуемое поведение структуры
Рассмотрим структуру:
struct eeh_type
{
uint16 size: 10; /* 10 bits */
uint16 code: 6; /* 6 bits */
};
В зависимости от компилятора-платформы , это может быть трактовано как
<10-bits><6-bits>
или
<6-bits><10-bits>
- Неопределенный порядок аргументов (
foo(pointer->member, pointer = &buffer[0]);
Компилятор gcc скушает и не поморщится , но не acc.
gcc берет аргументы слева направо , в то время как acc - справа налево .
Кстати , K&R and ANSI/ISO C не определяет этот порядок . Он может быть слева-направо ,
справа-налево или вообще каким угодно .
- Неправильный блок кода
if( ... )
foo();
else
bar();
и сравните вот с этим
if( ... )
foo();
/* the importance of this semicolon can't be overstated */
else
printf( "Calling bar()" ); /* oops! the else stops here */
bar(); /* oops! bar is always executed */
За такое конечно надо руки отрывать ...
- Permissive compilation (suggested by James M.
Stern
<jstern@world.nad.northrop.com>)
Однажды я модифицировал код через макрос следующим образом :
CALLIT(functionName,(arg1,arg2,arg3));
Затем я удалаил вызов CALLIT:
functionName,(arg1,arg2,arg3);
Функция перестала вызываться.
Или так:
switch (a) {
int var = 1; /* Этой инициализации как правило не суждено сбыться. */
/* The compiler doesn't complain, but it sure screws things up! */
case A: ...
case B: ...
}
Или попробуйте так:
#define DEVICE_COUNT 4
uint8 *szDevNames[DEVICE_COUNT] = {
"SelectSet 5000",
"SelectSet 7000"}; /* table has two entries of junk */
- Незащищенные возвращаемые данные
char *f() {
char result[80];
sprintf(result,"anything will do");
return(result); /* Oops! result
- то в стеке. */
}
int g()
{
char *p;
p = f();
printf("f() returns: %s\n",p);
}
Все будет хорошо до тех пор , пока кто-нибудь не забьет стек ...
- Неопределенные побочные эффекты
Например, I/++I может быть как 0 так и 1.
Или:
#include<stdio .h>
int foo(int n) {printf("Foo got %d\n", n); return(0);}
int bar(int n) {printf("Bar got %d\n", n); return(0);}
int main(int argc, char *argv[])
{
int m = 0;
int (*(fun_array[3]))();
int i = 1;
int ii = i/++i;
printf("\ni/++i = %d, ",ii);
fun_array[1] = foo; fun_array[2] = bar;
(fun_array[++m])(++m);
}
Prints either i/++i = 1 or i/++i=0;
Может быть отпечатано "Foo got 2", а может быть "Bar got 2"
- Непроинициализированные локальные переменные
Бага широко известная . Рассмотрим пример:
void foo(a)
{ int b;
if(b) {/* bug! b is not initialized! */ }
}
Или:
void foo(int a)
{ BYTE *B;
if(a) B=Malloc(a);
if(B) { /* BUG! B may or may not be initialized */ *b=a; }
}
- Cluttered compile time environment
Рассмотрим пример :
#include <stdio.h>
#define BUFFSIZE 2048
long
foo[BUFSIZ];
//note spelling of BUFSIZ != BUFFSIZE
Компиляция пройдет без ошибок , но ошибка в том , что BUFSIZ уже определена в stdio.h.
Иногда подобные ошибки трудно отискать.
- Underconstrained fundamental types
Не секрет , что в зависимости от платформы или версии компилятора
размер фундаментального типа int может быть 16 , а может и 32 бита..
- Utterly unsafe arrays
Пример :
int thisIsNuts[4]; int i;
for ( i = 0; i < 10; ++i )
{
thisIsNuts[ i ] = 0; /*
Недурно , не правда ли ? Я использую элементы с порядковыми номерами 1-10 при том , что массив-то всего из 4 элементов
*/
}
- Octal numbers
В C, числа начинающиеся с нуля , ассоциируются с системой счисления 8.
int numbers[] = {
001, // красиво , но не рационально
010, // 8 , а не 10
014 }; // 12, а не 14
- Signed Characters/Unsigned bytes.
В C введен unsigned для целых типов.
С другой стороны , тот факт , что тип char или byte может быть знаковым ,
тоже является своего рода проблемой .
char s = 127;
unsigned char u = 127;
s++; /* результат - отрицательное число! */
if (s<u) { /* true!*/ }
if(s>127) { /* this can never be true */ }
if(u<0) { /* this can never be true*/ }