Michael Brudno 8/3/00 More C ---- - From Tuesday's lecture you may have gotten the incorrect impression that I don't like C. I like it. It is a very compact, elegant, language which I code in more than in any other. Yes, it is hard to debug, but the advantages which it gives you in terms of power are definetly worth it. It is the ++ part of C++ which I don't like. But more on that below. Pointer Arithmetic ------- ---------- In C you can not only add numbers, you can add pointers (which are numbers, after all). Consider the following sequence of definitions: int* c = (int*) malloc(5*sizeof(int)); *c = 5; /* This is the same as c[0] = 5; c++; /* This means make c point to the next integer */ c[-1] == 5 /* this is true */ Lets take a simple example. Give the array A we want to find the maximum number in it. int i, max = A[0]; for (i = 1; i < sizeOfA; i++) if (A[i] > max) max = A[i]; All well and good. Now for the pointer version. int *i, *max=A; for(i=A; i *max) max = i; Aaack! And all this does is save a few operations. Don't write code like this! Obfuscated C ---------- - On the web there is a page dedicated to bad C code. It is bad. Really bad. If you want to see hwo bad for yourself go to www.ioccc.org. IOCCC is the obfuscated c-code contest. Although completely useless, it is something fun to look at. Here are a few classics: /* This will count gotos in an input file. Remember, gotos are considered harmful... */ #include #include main(togo,toog) int togo; char *toog[]; {char *ogto, tgoo[80]; FILE *ogot; int oogt=0, ootg, otog=79, ottg=1;if ( togo== ottg) goto gogo; goto goog; ggot: if ( fgets( tgoo, otog, ogot)) goto gtgo; goto gott; gtot: exit(); ogtg: ++oogt; goto ogoo; togg: if ( ootg > 0) goto oggt; goto ggot; ogog: if ( !ogot) goto gogo; goto ggto; gtto: printf( "%d goto \'s\n", oogt); goto gtot; oggt: if ( !memcmp( ogto, "goto", 4)) goto otgg; goto gooo; gogo: exit( ottg); tggo: ootg= strlen(tgoo); goto tgog; oogo: --ootg; goto togg; gooo: ++ogto; goto oogo; gott: fclose( ogot); goto gtto; otgg: ogto= ogto +3; goto ogtg; tgog: ootg-=4;goto togg; gtgo: ogto= tgoo; goto tggo; ogoo: ootg-=3; goto gooo; goog: ogot= fopen( toog[ ottg], "r"); goto ogog; ggto: ogto= tgoo; goto ggot;} /* This will print out pi to 3 decimal places. How? No clue. */ #define _ -F<00||--F-OO--; int F=00, OO=00;main( ) {F_OO();printf("%1.3f\n",4.*-F/OO/OO);}F_OO() { _-_-_-_ _-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_-_-_-_-_ _-_-_-_-_-_-_-_ _-_-_-_ }> /* This collection of characters plays tetris */ long h[4];t(){h[3]-=h[3]/3000;setitimer(0,h,0);}c,d,l,v[]={(int)t,0,2},w,s,I, K=0,i=276,j,k,q[276],Q[276],*n=q,*m,x=17,f[]={7,-13,-12,1,8,-11,-12,-1,9,-1,1, 12,3,-13,-12,-1,12,-1,11,1,15,-1,13,1,18,-1,1,2,0,-12,-1,11,1,-12,1,13,10,-12, 1,12,11,-12,-1,1,2,-12,-1,12,13,-12,12,13,14,-11,-1,1,4,-13,-12,12,16,-11,-12, 12,17,-13,1,-1,5,-12,12,11,6,-12,12,24};u(){for(i=11;++i<264;)if((k=q[i])-Q[i] ) {Q[i]=k;if(i-++I||i%12><1)printf("\033[%d;%dH",(I=i)/12,i%12*2+28); printf("\033[%dm " +( K-k?0:5 ) , k ) ;K=k;}Q[263]=c=getchar( );} G(b){for(i=4;i--; ) if( q[i?b+ n[i]:b] ) return 0;return 1;}g( b ){ for(i=4;i--;q[i?x+n[i]:x]=b) ;}main(C,V,a)char* *V ,*a;{h[3]=1000000/ ( l=C>1?atoi(V[1]):2);for(a=C>2?V[2]:"jklpq";i;i--)*n++=i< 25||i%12<2?7:0; srand(getpid());system("stty cbreak-echo stop u" ) ;sigvec(14,v, 0 ) ;t();puts("\033[H\033[J");for(n=f+rand( )%7*4;;g(7),u(),g(0)){if(c><0){ if(G(x+ 12 ) ) x+=12;else{g( 7 );++w;for(j=0;j><252;j=12*(j/12+1)) for(;q[++j];)if(j%12==10){ for(;j%12;q[j--]=0 ) ;u();for(;--j;q[j+12]=q[j] ) ; u();}n=f+rand( )%7*4;G(x=17 ) ||(c =a[5]);}}if(c==*a)G(--x)||++x;if(c==a[1]) n=f+4**(m=n) , G( x ) ||(n=m ) ;if(c==a[2])G ( ++x )||--x;if(c==a[3]) for(;G(x+12);++w)x+=12;if( c==a[4]||c==a[5]) {s=sigblock( 8192 ); printf("\033[H\033[J\033[0m%d\n",w);if(c==a[5])break;for(j=264;j--;Q[j]=0); while(getchar()-a[4]);puts("\033[H\033[J\033[7m");sigsetmask(s);}} d=popen("stty -cbreak echo stop \023;cat - HI|sort -rn|head -20>/tmp/$$;mv /tmp/$$ HI\ ;cat HI" , "w" ) ; fprintf(d,"%4d on level %1d by %s\n",w,l,getlogin());pclose(d);}> Why am I show this to you? To prove a very simple point. Writing ugly code in C/C++ is easy. Noone will think you are a better programmer because you use some archaic feature which speeds up your program by 3%. Cleanly written code is best. If, however, your urge to write bad code is too strong, submit it to IOCCC. They have a contest every year. C++ SUCKS Section by Jonathan Shewchuk ========= Over the past few lectures, I've tried to drop occasional hints that C++ is not quite the miracle it is sometimes rumored to be. Perhaps some of you have come to suspect that I might harbor some uncertainty, even disdain, toward the language. I must hasten to plead that it is not I alone who entertains, shall we say, suspicions. A more articulate explanation than I can give of our want of enthusiasm is offered by Ian Joyner at "http://www.elj.com/cppcv3/". To be precise, the kindly but fair Mr. Joyner offers a 60-page critique that reveals C++ to be, shall we say, imperfect. That the shortcomings of C++ take 60 pages to enumerate is, frankly, discomfiting. As an encore to my previous harangues, I thought I'd present four more C++ flaws. The first pertains to C as well. The second describes how operator overloading can allow your so-called colleague to change the semantics of the C++ language under your feet. The third demonstrates how the attempt to shoehorn new ideas (objects) into an old syntax (C's) can create confusing inconsistencies. The fourth is related to C++ inheritance, which is where many of the language's worst flaws reside. Assignment vs. Equality ----------------------- In most languages, it's easy to get assignment and equality mixed up. In C/C++, where "=" is assignment (though to the rest of civilization it's equality), confusing the two is even easier. A language should be designed to catch such errors. Unfortunately, in C/C++, "==" is just an operator that returns "1" if its operands are equal, "0" if not. The "if" statement and other conditionals interpret zero as falsity, and any other number as truth. Hence, if you write if (x = y) { ... } you have not, in the compiler's eyes, committed an error. The compiler will set x equal to y, and will execute the clause if y is non-zero. A good C compiler might print a warning message to ask if you perhaps meant "==", but it will still produce a program. Some C compilers won't warn you at all. (Java, happily, won't compile this statement unless x happens to be a boolean.) Conversely, if you write x == y; you have written a perfectly valid C statement that does nothing. Operator Overloading -------------------- A feature of C++ you will often hear sold as a good thing is the ability to redefine operators such as +, =, ++, and [] to have special meanings. Dancer &Dancer::operator+(Dancer &d) { ... } This allows you to write strings like "d1 + d2" instead of "d1.add(d2)". The advantage is that your code is...slighly shorter. The danger is that, in many cases, it's not apparent that a function is being called--especially when the = (assignment) operator is overloaded. This makes maintainability and modularity difficult, because programmers rarely think to include assignment in their interface descriptions. One team member may not know that another team member has changed the meaning of the assignment operator, and thus have to deal with very mysterious bugs. Recall that the same problem arises from the use of copy constructors in initializers (like "Dancer x = y;"). C++ Syntax at its Slobbering Foulest ------------------------------------ A C++ object whose constructor takes parameters is constructed much like a Java object. Dancer *michaelJackson = new Dancer(36000000, weird); However, if the constructor takes no parameters, the parentheses are optional. Dancer *reindeer = new Dancer(); // Constructor is called Dancer *solidGold = new Dancer; // No (), but constructor is called anyway You can also declare a C++ object on the stack. Even though it's on the stack, a constructor is still called if one with the appropriate signature exists. If the constructor takes parameters, these are passed as usual. Dancer markMorris(0, excellent); // Constructor is called However, if the constructor takes no parameters, you may NOT use parentheses. Dancer johnTravolta; // Constructor is called Why? If you did use parentheses, you would be doing something completely different from what you'd intended to do. The reason is because this syntax was already reserved for a completely different purpose in C. Dancer johnCandy(); // No constructor, no variable declared, no satisfaction! Can you tell why? C++ Member Functions Aren't Virtual by Default, but Can Be Replaced ------------------------------------------------------------------- The nature of inheritance in Java, and of virtual functions in C++, is that a method/member function can be overridden in an inherited class. Two or more methods having the same name exist, but the compiler calls the right one according to the type of the _object_ with which the method is invoked. However, member functions are not virtual by default in C++. If you forget to declare a member function virtual, and then try to override it later, it will really be _replaced_. Replacement is different from overriding: the member function that's chosen is determined not by the dynamic type (the class of the object), but by the static type of the variable with which the method is invoked. class X { | class Y : public X { public: | public: virtual void virt(); | void virt(); // overrides X:virt() void nonvirt(); | void nonvirt(); // replaces X:nonvirt() } | } Y y; X *xp = &y; // Both pointers point to an object of type Y. Y *yp = &y; yp->virt(); // Calls Y:virt(), as you would expect for objects of class Y. xp->virt(); // Calls Y:virt(), as you would expect for objects of class Y. xp->nonvirt(); // Calls X:nonvirt(), even though the object is of class Y!!!! Why is this bad? It makes it very difficult for a programmer (especially one who didn't write these classes) to determine which function is being called in a particular context. The information required to figure out which member function is being invoked by "xp->nonvirt();" may be spread over several files. Even though "nonvirt()" is defined in class Y, a programmer who's studied class Y cannot be sure how "xp->nonvirt()" behaves without looking at class X as well. A programmer who isn't aware of this quirk of C++, or who forgets to look, might create some truly mystifying bugs. Another reason why it's bad: you can accidentally replace a member function in a superclass if you don't realize it's there, by unwittingly creating a new member function with the same name. The compiler won't warn you.