What belongs in an educational tool to demonstrate the unwarranted assumptions people make in C/C++?

I'd like to prepare a little educational tool for SO which should help beginners (and intermediate) programmers to recognize and challenge their unwarranted assumptions in C, C++ and their platforms.

Examples:

  • "integers wrap around"
  • "everyone has ASCII"
  • "I can store a function pointer in a void*"
  • I figured that a small test program could be run on various platforms, which runs the "plausible" assumptions which are, from our experience in SO, usually made by many inexperienced/semiexperienced mainstream developers and record the ways they break on diverse machines.

    The goal of this is not to prove that it is "safe" to do something (which would be impossible to do, the tests prove only anything if they break), but instead to demonstrate to even the most uncomprehending individual how the most inconspicuous expression break on a different machine, if it has a undefined or implementation defined behavior. .

    To achieve this I would like to ask you:

  • How can this idea be improved?
  • Which tests would be good and how should they look like?
  • Would you run the tests on the platforms you can get your hands on and post the results, so that we end up with a database of platforms, how they differ and why this difference is allowed?

  • Here's the current version for the test toy:

    #include <stdio.h>
    #include <limits.h>
    #include <stdlib.h>
    #include <stddef.h>
    int count=0;
    int total=0;
    void expect(const char *info, const char *expr)
    {
        printf("..%sn   but '%s' is false.n",info,expr);
        fflush(stdout);
        count++;
    }
    #define EXPECT(INFO,EXPR) if (total++,!(EXPR)) expect(INFO,#EXPR)
    
    /* stack check..How can I do this better? */
    ptrdiff_t check_grow(int k, int *p)
    {
        if (p==0) p=&k;
        if (k==0) return &k-p;
        else return check_grow(k-1,p);
    }
    #define BITS_PER_INT (sizeof(int)*CHAR_BIT)
    
    int bits_per_int=BITS_PER_INT;
    int int_max=INT_MAX;
    int int_min=INT_MIN;
    
    /* for 21 - left to right */
    int ltr_result=0;
    unsigned ltr_fun(int k)
    {
        ltr_result=ltr_result*10+k;
        return 1;
    }
    
    int main()
    {
        printf("We like to think that:n");
        /* characters */
        EXPECT("00 we have ASCII",('A'==65));
        EXPECT("01 A-Z is in a block",('Z'-'A')+1==26);
        EXPECT("02 big letters come before small letters",('A'<'a'));
        EXPECT("03 a char is 8 bits",CHAR_BIT==8);
        EXPECT("04 a char is signed",CHAR_MIN==SCHAR_MIN);
    
        /* integers */
        EXPECT("05 int has the size of pointers",sizeof(int)==sizeof(void*));
        /* not true for Windows-64 */
        EXPECT("05a long has at least the size of pointers",sizeof(long)>=sizeof(void*));
    
        EXPECT("06 integers are 2-complement and wrap around",(int_max+1)==(int_min));
        EXPECT("07 integers are 2-complement and *always* wrap around",(INT_MAX+1)==(INT_MIN));
        EXPECT("08 overshifting is okay",(1<<bits_per_int)==0);
        EXPECT("09 overshifting is *always* okay",(1<<BITS_PER_INT)==0);
        {
            int t;
            EXPECT("09a minus shifts backwards",(t=-1,(15<<t)==7));
        }
        /* pointers */
        /* Suggested by jalf */
        EXPECT("10 void* can store function pointers",sizeof(void*)>=sizeof(void(*)()));
        /* execution */
        EXPECT("11 Detecting how the stack grows is easy",check_grow(5,0)!=0);
        EXPECT("12 the stack grows downwards",check_grow(5,0)<0);
    
        {
            int t;
            /* suggested by jk */
            EXPECT("13 The smallest bits always come first",(t=0x1234,0x34==*(char*)&t));
        }
        {
            /* Suggested by S.Lott */
            int a[2]={0,0};
            int i=0;
            EXPECT("14 i++ is strictly left to right",(i=0,a[i++]=i,a[0]==1));
        }
        {
            struct {
                char c;
                int i;
            } char_int;
            EXPECT("15 structs are packed",sizeof(char_int)==(sizeof(char)+sizeof(int)));
        }
        {
            EXPECT("16 malloc()=NULL means out of memory",(malloc(0)!=NULL));
        }
    
        /* suggested by David Thornley */
        EXPECT("17 size_t is unsigned int",sizeof(size_t)==sizeof(unsigned int));
        /* this is true for C99, but not for C90. */
        EXPECT("18 a%b has the same sign as a",((-10%3)==-1) && ((10%-3)==1));
    
        /* suggested by nos */
        EXPECT("19-1 char<short",sizeof(char)<sizeof(short));
        EXPECT("19-2 short<int",sizeof(short)<sizeof(int));
        EXPECT("19-3 int<long",sizeof(int)<sizeof(long));
        EXPECT("20 ptrdiff_t and size_t have the same size",(sizeof(ptrdiff_t)==sizeof(size_t)));
    #if 0
        {
            /* suggested by R. */
            /* this crashed on TC 3.0++, compact. */
            char buf[10];
            EXPECT("21 You can use snprintf to append a string",
                   (snprintf(buf,10,"OK"),snprintf(buf,10,"%s!!",buf),strcmp(buf,"OK!!")==0));
        }
    #endif
    
        EXPECT("21 Evaluation is left to right",
               (ltr_fun(1)*ltr_fun(2)*ltr_fun(3)*ltr_fun(4),ltr_result==1234));
    
        {
        #ifdef __STDC_IEC_559__
        int STDC_IEC_559_is_defined=1;
        #else 
        /* This either means, there is no FP support
         *or* the compiler is not C99 enough to define  __STDC_IEC_559__
         *or* the FP support is not IEEE compliant. */
        int STDC_IEC_559_is_defined=0;
        #endif
        EXPECT("22 floating point is always IEEE",STDC_IEC_559_is_defined);
        }
    
        printf("From what I can say with my puny test cases, you are %d%% mainstreamn",100-(100*count)/total);
        return 0;
    }
    

    Oh, and I made this community wiki right from the start because I figured that people want to edit my blabber when they read this.

    UPDATE Thanks for your input. I've added a few cases from your answers and will see if I can set up a github for this like Greg suggested.

    UPDATE : I've created a github repo for this, the file is "gotcha.c":

  • http://github.com/lutherblissett/disenchanter
  • Please answer here with patches or new ideas, so they can be discussed or clarified here. I will merge them into gotcha.c then.


    The order of evaluation of subexpressions, including

  • the arguments of a function call and
  • operands of operators (eg, + , - , = , * , / ), with the exception of:
  • the binary logical operators ( && and || ),
  • the ternary conditional operator ( ?: ), and
  • the comma operator ( , )
  • is Unspecified

    For example

      int Hello()
      {
           return printf("Hello"); /* printf() returns the number of 
                                      characters successfully printed by it
                                   */
      }
    
      int World()
      {
           return printf("World !");
      }
    
      int main()
      {
    
          int a = Hello() + World(); //might print Hello World! or World! Hello
          /**             ^
                          | 
                    Functions can be called in either order
          **/
          return 0;
      } 
    


    sdcc 29.7/ucSim/Z80

    We like to think that:
    ..09a minus shifts backwards
       but '(t=-1,(15<<t)==7)' is false.
    ..19-2 short<int
       but 'sizeof(short)<sizeof(int)' is false.
    ..22 floating point is always IEEE
       but 'STDC_IEC_559_is_defined' is false.
    ..25 pointer arithmetic works outside arrays
       but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
    From what I can say with my puny test cases, you are Stop at 0x0013f3: (106) Invalid instruction 0x00dd
    

    printf crashes. "O_O"


    gcc 4.4@x86_64-suse-linux

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..08 overshifting is okay
    but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..14 i++ is strictly left to right
    but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
    but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
    but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..26 sizeof() does not evaluate its arguments
    but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
    From what I can say with my puny test cases, you are 79% mainstream
    

    gcc 4.4@x86_64-suse-linux(-O2)

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..08 overshifting is okay
    but '(1<<bits_per_int)==0' is false.
    ..14 i++ is strictly left to right
    but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
    but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
    but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..26 sizeof() does not evaluate its arguments
    but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
    From what I can say with my puny test cases, you are 82% mainstream
    

    clang 2.7@x86_64-suse-linux

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..08 overshifting is okay
    but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..14 i++ is strictly left to right
    but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
    but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
    but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..21a Function Arguments are evaluated right to left
    but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
    ltr_result is 1234 in this case
    ..25a pointer arithmetic works outside arrays
    but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
    ..26 sizeof() does not evaluate its arguments
    but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
    From what I can say with my puny test cases, you are 72% mainstream
    

    open64 4.2.3@x86_64-suse-linux

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..08 overshifting is okay
    but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..15 structs are packed
    but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
    but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..21a Function Arguments are evaluated right to left
    but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
    ltr_result is 1234 in this case
    ..25a pointer arithmetic works outside arrays
    but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
    ..26 sizeof() does not evaluate its arguments
    but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
    From what I can say with my puny test cases, you are 75% mainstream
    

    intel 11.1@x86_64-suse-linux

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..08 overshifting is okay
    but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..14 i++ is strictly left to right
    but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
    but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
    but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..21a Function Arguments are evaluated right to left
    but '(gobble_args(0,ltr_fun(1),ltr_fun(2),ltr_fun(3),ltr_fun(4)),ltr_result==4321)' is false.
    ltr_result is 1234 in this case
    ..26 sizeof() does not evaluate its arguments
    but '(i=10,sizeof(char[((i=20),10)]),i==10)' is false.
    From what I can say with my puny test cases, you are 75% mainstream
    

    Turbo C++/DOS/Small Memory

    We like to think that:
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..16 malloc()=NULL means out of memory
    but '(malloc(0)!=NULL)' is false.
    ..19-2 short<int
    but 'sizeof(short)<sizeof(int)' is false.
    ..22 floating point is always IEEE
    but 'STDC_IEC_559_is_defined' is false.
    ..25 pointer arithmetic works outside arrays
    but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
    ..25a pointer arithmetic works outside arrays
    but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
    From what I can say with my puny test cases, you are 81% mainstream
    

    Turbo C++/DOS/Medium Memory

    We like to think that:
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..10 void* can store function pointers
    but 'sizeof(void*)>=sizeof(void(*)())' is false.
    ..16 malloc()=NULL means out of memory
    but '(malloc(0)!=NULL)' is false.
    ..19-2 short<int
    but 'sizeof(short)<sizeof(int)' is false.
    ..22 floating point is always IEEE
    but 'STDC_IEC_559_is_defined' is false.
    ..25 pointer arithmetic works outside arrays
    but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
    ..25a pointer arithmetic works outside arrays
    but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
    From what I can say with my puny test cases, you are 78% mainstream
    

    Turbo C++/DOS/Compact Memory

    We like to think that:
    ..05 int has the size of pointers
    but 'sizeof(int)==sizeof(void*)' is false.
    ..09a minus shifts backwards
    but '(t=-1,(15<<t)==7)' is false.
    ..16 malloc()=NULL means out of memory
    but '(malloc(0)!=NULL)' is false.
    ..19-2 short<int
    but 'sizeof(short)<sizeof(int)' is false.
    ..20 ptrdiff_t and size_t have the same size
    but '(sizeof(ptrdiff_t)==sizeof(size_t))' is false.
    ..22 floating point is always IEEE
    but 'STDC_IEC_559_is_defined' is false.
    ..25 pointer arithmetic works outside arrays
    but '(diff=&var.int2-&var.int1, &var.int1+diff==&var.int2)' is false.
    ..25a pointer arithmetic works outside arrays
    but '(diff=&p1-&p2, &p2+diff==&p1)' is false.
    From what I can say with my puny test cases, you are 75% mainstream
    

    cl65@Commodore PET (vice emulator)

    alt text http://i34.tinypic.com/2hh0zmc.png


    I'll be updating these later:


    Borland C++ Builder 6.0 on Windows XP

    ..04 a char is signed
       but 'CHAR_MIN==SCHAR_MIN' is false.
    ..08 overshifting is okay
       but '(1<<bits_per_int)==0' is false.
    ..09 overshifting is *always* okay
       but '(1<<BITS_PER_INT)==0' is false.
    ..09a minus shifts backwards
       but '(t=-1,(15<<t)==7)' is false.
    ..15 structs are packed
       but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..16 malloc()=NULL means out of memory
       but '(malloc(0)!=NULL)' is false.
    ..19-3 int<long
       but 'sizeof(int)<sizeof(long)' is false.
    ..22 floating point is always IEEE
       but 'STDC_IEC_559_is_defined' is false.
    From what I can say with my puny test cases, you are 71% mainstream
    

    Visual Studio Express 2010 C++ CLR, Windows 7 64bit

    (must be compiled as C++ because the CLR compiler does not support pure C)

    We like to think that:
    ..08 overshifting is okay
       but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
       but '(t=-1,(15<<t)==7)' is false.
    ..14 i++ is structly left to right
       but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
       but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..19-3 int<long
       but 'sizeof(int)<sizeof(long)' is false.
    ..22 floating point is always IEEE
       but 'STDC_IEC_559_is_defined' is false.
    From what I can say with my puny test cases, you are 78% mainstream
    

    MINGW64 (gcc-4.5.2 prerelase)

    -- http://mingw-w64.sourceforge.net/

    We like to think that:
    ..05 int has the size of pointers
       but 'sizeof(int)==sizeof(void*)' is false.
    ..05a long has at least the size of pointers
       but 'sizeof(long)>=sizeof(void*)' is false.
    ..08 overshifting is okay
       but '(1<<bits_per_int)==0' is false.
    ..09a minus shifts backwards
       but '(t=-1,(15<<t)==7)' is false.
    ..14 i++ is structly left to right
       but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
       but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..17 size_t is unsigned int
       but 'sizeof(size_t)==sizeof(unsigned int)' is false.
    ..19-3 int<long
       but 'sizeof(int)<sizeof(long)' is false.
    ..22 floating point is always IEEE
       but 'STDC_IEC_559_is_defined' is false.
    From what I can say with my puny test cases, you are 67% mainstream
    

    64 bit Windows uses the LLP64 model: Both int and long are defined as 32-bit, which means that neither is long enough for a pointer.


    avr-gcc 4.3.2 / ATmega168 (Arduino Diecimila)

    The failed assumptions are:

    ..14 i++ is structly left to right
    ..16 malloc()=NULL means out of memory
    ..19-2 short<int
    ..21 Evaluation is left to right
    ..22 floating point is always IEEE
    

    The Atmega168 has a 16 bit PC, but code and data are in separate address spaces. Larger Atmegas have a 22 bit PC!.


    gcc 4.2.1 on MacOSX 10.6, compiled with -arch ppc

    We like to think that:
    ..09a minus shifts backwards
       but '(t=-1,(15<<t)==7)' is false.
    ..13 The smallest bits come always first
       but '(t=0x1234,0x34==*(char*)&t)' is false.
    ..14 i++ is structly left to right
       but '(i=0,a[i++]=i,a[0]==1)' is false.
    ..15 structs are packed
       but 'sizeof(char_int)==(sizeof(char)+sizeof(int))' is false.
    ..19-3 int<long
       but 'sizeof(int)<sizeof(long)' is false.
    ..22 floating point is always IEEE
       but 'STDC_IEC_559_is_defined' is false.
    From what I can say with my puny test cases, you are 78% mainstream
    


    A long time ago, I was teaching C from a textbook that had

    printf("sizeof(int)=%dn", sizeof(int));
    

    as a sample question. It failed for a student, because sizeof yields values of type size_t , not int , int on this implementation was 16 bits and size_t was 32, and it was big-endian. (The platform was Lightspeed C on 680x0-based Macintoshes. I said it was a long time ago.)

    链接地址: http://www.djcxy.com/p/12728.html

    上一篇: 对C ++中的变量进行多次预增量操作(C?)

    下一篇: 什么属于教育工具来演示人们在C / C ++中做出的无理假设?