C语言10个烦人的地方


To get on this list, a bug has to be able to cause at least half a day of futile head scratching, and has to be aggravated by the poor design of the "C" language.  In the interests of equal time, and to see how the world has progressed in the 20-odd years since "C" escaped from its spawning ground, see my Top 10 Ways to be Screwed by the Java programming language, and for more general ways to waste a lot of time due to bad software, try my Adventures in Hell page.
A better language would allow fallible programmers to be more productive. Infallible programmers, of the type unix' and "C" designers anticipated, need read no further.  In fairness, I have to admit that the writers of compilers have improved on the situation in recent years, by detecting and warning about potentially bad code in many cases.


Non-terminated comment, "accidentally" terminated by some subsequent comment, with the code in between swallowed.
        a=b; /* this is a bug
        c=d; /* c=d will never happen */
Accidental assignment/Accidental Booleans
        if(a=b) c;      /* a always equals b, but c will be executed if b!=0 */
Depending on your viewpoint, the bug in the language is that the assignment operator is too easy to confuse with the equality operator; or maybe the bug is that C doesn't much care what constitutes a boolean expression: (a=b) is not a boolean expression! (but C doesn't care).
Closely related to this lack of rigor in booleans, consider this construction: 
        if( 0 < a < 5) c;      /* this "boolean" is always true! */
Always true because (0<a) generates either 0 or 1 depending on if (0<a), then compares the result to 5, which is always true, of course.  C doesn't really have boolean expressions, it only pretends to. 


Or consider this:


        if( a =! b) c;      /* this is compiled as (a = !b), an assignment, rather than (a != b) or (a == !b) */


Unhygienic macros
        #define assign(a,b) a=(char)b
        assign(x,y>>8)
becomes 
               x=(char)y>>8    /* probably not what you want */ 
 
Mismatched header files


Suppose foo.h contains:
        struct foo { BOOL a};


  file F1.c  contains
        #define BOOL char
        #include "foo.h"


  file F2.c contains 
        #define BOOL int
        #include "foo.h"
now, F1. and F2 disagree about the fundamental attributes of structure "foo". If they talk to each other, You Lose! 
 
Phantom returned values


Suppose you write this
    int foo (a)
    { if (a) return(1); } /* buggy, because sometimes no value is returned  */
Generally speaking, C compilers, and C runtimes either can't or don't tell you there is anything wrong. What actually happens depends on the particular C compiler and what trash happened to be left lying around wherever the caller is going to look for the returned value. Depending on how unlucky you are, the program may even appear to work for a while.
Now, imagine the havoc that can ensue if "foo" was thought to return a pointer! 
 


Unpredictable struct construction


Consider this bit packing struct:
    struct eeh_type
    {
            uint16 size:          10;   /* 10 bits */
            uint16 code:           6;   /* 6 bits */
    };
Depending on which C compiler, and which "endian" flavor of machine you are on, this might actually be implemented as
        <10-bits><6-bits>
or as
        <6-bits><10-bits>


Also, again depending on the C compiler, machine architecture, and various mysterious preference settings,
the items might be aligned to the nearest 8, 16, 32, or 64 bits.


So what matters? If you are trying to match bits with a real world file,
everything!




Need another way to lose big?  How about this:


Rect foo = {0,1,2,3}; // assign numbers to the first four slots


You may think you know what those four slots are, but there's at least an
even chance you'll have to discover the hard way if the structure ever
changes.


Indefinite order of evaluation (contributed by Xavier @ triple-i.com)
        foo(pointer->member, pointer = &buffer[0]);
Works with gcc (and other compilers I used until I tried acc) and does not with acc. The reason is that gcc evaluates function arguments from left to right, while acc evaluates arguments from right to left.
K&R and ANSI/ISO C specifications do not define the order of evaluation for function arguments. It can be left-to-right, right-to-left or anything else and is "unspecified". Thus any code which relies on this order of evaluation is doomed to be non portable, even across compilers on the same platform.


This isn't an entirely non controversial point of view. Read the supplementary dialog on the subject. 
 


Easily changed block scope (Suggested by Marcel van der Peijl )
    if( ... ) 
        foo(); 
    else 
        bar();
which, when adding debugging statements, becomes
    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 */
There is a large class of similar errors, involving misplaced semicolons and brackets. 
 
Permissive compilation (suggested by James M. Stern)


I once modified some code that called a function via a macro:
        CALLIT(functionName,(arg1,arg2,arg3));
CALLIT did more than just call the function. I didn't want to do the extra stuff so I removed the macro invocation, yielding:
        functionName,(arg1,arg2,arg3);
Oops. This does not call the function. It's a comma expression that:
Evaluates and then discards the address of functionName
Evaluates the parenthesized comma expression (arg1,arg2,arg3)
C's motto: who cares what it means? I just compile it! My own favorite in this vein is this:
        switch (a) {
        int var = 1;    /* This initialization typically does not happen. */
                        /* The compiler doesn't complain, but it sure screws things up! */
        case A: ...
        case B: ...
        }
Still not convinced? Try this one (suggested by Mark Scarbrough ):
#define DEVICE_COUNT 4 
uint8 *szDevNames[DEVICE_COUNT] = {
        "SelectSet 5000",
        "SelectSet 7000"}; /* table has two entries of junk */
Unsafe returned values (suggested by Bill Davis <wdavis@dw3f.ess.harris.com>) 
char *f() { 
   char result[80]; 
   sprintf(result,"anything will do"); 
   return(result);    /* Oops! result is allocated on the stack. */ 
 }


int g() 

   char *p; 
   p = f(); 
   printf("f() returns: %s\n",p); 

The "wonderful" thing about this bug is that it sometimes seems to be a correct program; As long as nothing has reused the particular piece of stack occupied by result. 
 


Undefined order of side effects. (suggested by michaelg@owl.WPI.EDU and others) 
Even within a single expression, even with only strictly manifest side effects, C doesn't define the order of the side effects. Therefore, depending on your compiler, I/++I might be either 0 or 1. Try this:


#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;
Prints either "Foo got 2", or "Bar got 2" 
 
Uninitialized local variables 
Actually, this bug is so well known, it didn't even make the list! That doesn't make it less deadly when it strikes. Consider the simplest case:


void foo(a)
{ int b;
  if(b) {/* bug! b is not initialized! */ }
}
and in truth, modern compilers will usually flag an error as blatant as the above. However, you just have to be a little more clever to outsmart the compiler. Consider:
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 
The compile-time environment of a typical compilation is cluttered with hundreds (or thousands!) of things that you typically have little or no awareness of.  These things sometimes have dangerously common names, leading to accidents that can be virtually impossible to spot.


#include <stdio.h>
#define BUFFSIZE 2048 
long foo[BUFSIZ];                //note spelling of BUFSIZ != BUFFSIZE


This compiles without error, but will fail in predictably awful and mysterious ways, because BUFSIZ is a symbol defined by stdio.h.  A typo/braino like this can be virtually impossible to find if the distance between the the #define and the error is greater than in this trivial example. 
 


Under constrained fundamental types


I've been seriously burned because different compilers, or even different options of the same compiler, define the fundamental type int as either 16 or 32 bits..  In the same vein, name any other language in which boolean might be defined or undefined, or might be defined by a compiler option, a runtime pragma (yes! we have booleans!), or just about any way the user decided would work ok. 
 
Utterly unsafe arrays 
This is so obvious it didn't even make the list for the first 5 years, but C's arrays and associated memory management are completely, utterly unsafe, and even obvious cases of error are not detected.


 int thisIsNuts[4]; int i; 
  for ( i = 0; i < 10; ++i ) 
  { 
    thisIsNuts[ i ] = 0;     /* Isn't it great ?  I can use elements 1-10 of a 4 element array, and no one cares */ 
  }


Of course, there are infinitely many ways to do things like this in C. 
 


Octal numbers (suggested by Paul C. Anagnostopoulos) 
In C, numbers beginning with a zero are evaluated in base 8.  If there are no 8's or 9's in the numbers, then there will be no complaints from the compiler, only screams from the programmer when he finally discovers the nature of the problem.  
 int numbers[] = { 001,        // line up numbers for typographical clarity, lose big time 
                           010,        // 8 not 10 
                           014 };     // 12, not 14 
Not convinced ? Try atoi("000010");
Signed Characters/Unsigned bytes.
C was forced into a consistency trap by including  unsigned as a modifier for all integer types.  On one hand, the fact that types char and byte are signed causes all kinds of problems -  It is never intuitive that 128 is a negative number, and so very easy to forget.  On the other hand,  any arithmetic using low precision integers must be done very carefully, and C makes it much too easy to ignore this.


char s = 127;
unsigned char u = 127;
s++;      /* the result is a negative number!  Effectively overflow occurs, but no trap */
if (s<u) { /* true!*/ }
if(s>127) { /* this can never be true */  }
if(u<0) {  /* this can never be true*/  }


Fabulously awful "standard libraries" (suggested by Pietro Gagliardi)
The default libraries in C are leftovers from the stone age of computing, when anything that worked was acceptable.  They are full of time bombs waiting to explode at runtime,  For an example, look no further than the "standard i/o library", which, amazingly, is still standard.
{ int a=1,b=2;
  char buf[10];
  scanf("%d %d",a,b); // don't you mean &a,&b?  Prepare to blow!
  sprintf(buf,"this is the result: %d %d"); // putting at least 20 characters in a 10 character buffer
// and fetching a couple random vars from the stack.
}
Accidental Integers


int a = 2 && 4 && 8; // what is the value of "a" ? 


would you belive a=1 ? that's correct! Why would anyone write something like this
anyway? Well, the actual expression I wrote was this:
int value = a && b && fn(a->x,b->x);


I had a function of two pointer fields, and I wanted to be "safe" against accindentally referencing through a null pointer.
Imagine my surprise when the value was always zero or one.  This one line of code illustrates a trifecta of C's follies: allowing
a pointer to be treated as a boolean,  treating an integer as a boolean, and treating a boolean as an integer.


64 Bit Madness (suggested by Andrey Karpov from viva64.com)
With the advent of 64 bit architectures, there are many new ways that C will screw you, especially in the "rare" case that array indeces approach or exceed 2^31.  The basic problem is that signed and unsigned 32 bit integers are accidents waiting to happen when used to index into large arrays.   These bugs are mostly theoretical, since most arrays will remain reasonably sized.  But expect a whole new universe of catastrophic failures of trusted and thoroughly debugged systems when they are stressed with large arrays.   Viva64 has a product to sell, but it looks like a good one.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值