I began writing code in the 1970s, under operating systems such as: MVS, VMS, NorthStar, DMF, CP/M, DOS and others. These relied on 80x24 text displays, did not have "mice" and had more limited file name capability. As a result, my "formative" years were at the "command line", and given some of these systems were quite limited, the C source code "style" I developed during these years may be somewhat different from what's common today: - C blocks - I tend to place { and } at the end of lines (so they don't take up more of the 24 I could see at once) To "keep track" of C block level, I use "tab depth" (tabs take less file space than multiple spaces but typical 8-space stops make the source lines "too long" - for this reason my editor defaults to 4 space stops). - you can use my TYPE4 utility to convert them to "properly spaced". - Long filenames - I tend not to use them - A programmer today, writing a program to test doing recursive directory scans, might call the source file: "Program to test recursive directory scans.c" Commands to edit, compile and run (to test) this program might be: > edt "Program to test recursive directory scans.c" > cc "Program to test recursive directory scans" > "Program to test recursive directory scans.c" During a "debugging session", I might do this dozens of times, and typing the above "over and over" would become tiring (not to mention error prone :-), so I tend to call most "test" programs T.C till I'm happy with them, then rename to something like: DirScn.C > edt t.c > cc t > t ... repeat ... > rename t.c DirScn.C - Symbol names - In a similar manner, I tend not to use huge long symbol names, eg: name_index = name_found = 0; while(name_index < name_list_top) { if(!strcmp(name_list_text[name_index++], temp_string)) name_found = name_index; } if(name_found != 0) { ... do stuff with name_list_text[name_found-1] ... } For me, this might be more like: i = j = 0; while(i < Ntop) { if(!strcmp(Names[i++], Temp)) j = i; } if(j) { ... do stuff with Names[j-1] ... } - GOTO and lables: Sometimes a "goto" makes sense to me. For example, to parse a DOS filespec extracting the filename and extension, I might do something like: i = Noffset = 0; a1: Eoffset = 0; // !Eoffset = NoExtension a2: switch(FileSpec[i++]) { case ':': case'\\': Noffset = i; goto a1; // Set offset to Name case '.': Eoffset = i; // "" to Extension default : goto a2; case 0 : ; } Note that I often use 'Xn' for such temp labels: Fits 4-space tab stop X'A-Z' allows multiple "sections" without renumber - signed .vs. unsigned - Many of the tiny systems I originally made my compiler (Micro-C) to build code for took more work/instructions to do some operations on signed variables ... for this reason I tend to use "unsigned" for variables which never go negative. - do .. while - In C most conditional expressions have to be enclosed within () so the compiler knows when the expression ends and the controlled statement begins: if(expression) statement; while(expression) statement; There is one case with expression at END; (like most statements): do ... while(expression); Other compilers require the (), Micro-C does not! It treats expression the same as it would: return expression; do ... while expression; which COULD have () with no effect: return(expression); do ... while(expression); I sometimes don't put () in these case! - ++i .vs. i++ - I never could fathom why some people when writing C code seem to think: {i} Gets incremented: i++; I tend to think: Increment {i}: ++i; * First editions of my compiler generated MORE code when ++ or -- followed a variable when the resulting value wasn't used! This was because on little processors it was originally designed for, such operations had to be done in a register! : ++i returns the NEW value of {i} while i++ gives the OLD value! My compiler didn't keep track of "where in the expression" and would generate: ++i; = load i / increment reg / store i // reg has NEW i++; = load i / increment reg / store i / decrement reg // "" OLD Later I adjusted NOT to recover the OLD value when it wasn't actually used, but the early behaviour helped cement the "Increment {i}" thinking which is how I still normally code simple incs/decs. - True=7 - Having lots of very low-level code and hardware experience, I developed a bit of tendancy to "minimize what can go wrong at low levels" - C treats 0==FALSE and !0==TRUE - most people use 0/1 ... but thats only 1 bit "difference". I sometimes use 7=TRUE as thats 3 bits with no more chars to type (and of course foolish as such a 1 bit hardware error would "trash" pretty much any system - but I do tend to be a creature of habit :) - K&R style - The first C compiler that I used (and early versions of Micro-C) did not support // comments, and allowed only names in function argument lists, with the actual declarations immediatly following: /* Sample program */ int main(argc, argv) int argc; char *argv[]; { ... } Instead of the more modern: // Sample program int main(int argc, char *argv[]) { ... } Therefore much of my early code (incl Micro-C itself) is the former style. - *** Micro-C compiler differences *** - I wrote Micro-C back in the 80's - when I was mainly interested in producing code for VERY small embedded systems. I had never written a compiler before and there are some differences/enhancements between Micro-C and standard C: - Local initializations - I wrote my compiler initially to run on very limited systems, IIRC the first version ran within a 32k memory block. It also had to produce code to run in as little space as reasonably possible, so I wanted all local stack space to be allocated in a single CPU instruction. For these reasons Micro-C does NOT record/keep track of initial values for local symbols and assign them after the stack is allocated. { unsigned i=0, j=0, k=1; ... So my code becomes: { unsigned i, j, k; i = j = 0; k = 1; This also means Micro-C expects all local variable to be declared at the beginning of a function (before any code). - register functions() - no varargs - Again, for efficiency on tiny systems, Micro-C passes (stacks) function args as they are parsed (in the order they appear). This keeps it from having to store the argument values so that it can stack them in reverse. This works fine for functions which have a fixed number of arguments, but can present a problem with "variable number of arguments" like printf(). Such functions typically decide the number of args to retrieve by examining those near the start of the argument lists - the location of which can only be known if args stacked in reverse (first closest to SP). Micro-C allows the "register" property to be applied to a function.. This causes the processor "accumulator" (register) to be loaded with the number of argumentgs passed immediatly before the function is CALLed. The library contains "nargs()" which "does nothing", allowing the value in accumulator to be accessed (and since locals stack is already reserved it can be assigned to a local variable); - my optimizer removes CALL nargs leaving accumulator as "return" value - register printf(unsigned args) { unsigned buf[128]; _format_(nargs()*2+&args, buf); // nargs() is FIRST thing fputs(buf, stdout); // in generated code } - && and || - Other compilers only accept && and || operators within conditional expressions. Micro-C makes them useful within non-conditionals: a && b === a ? b : a; a || b === a ? a : b; just like conditionals they "short circuit": a && b -- only executes b if a I do (rarely) use this: putc(c || '\n', fp); and (more rarely): a && (a=1); // if(a) a=1; a || (a=1); // if(!a) a=1; - "string1"#..."string2 - My preprocessor (MCP) has an extension to string handling. Basically if a string is immediately followed by '#' then the closing '"' and the next '"' occuring in the source are "removed". This is mainly to let you put #defined values within strings: #define VERSION 5 // !"" can be tested arithmetically! ... fputs("Version "#VERSION"."); // prints "Version 5." Dave Dunfield - https://dunfield.themindfactory.com Search "Dave's Old Computers" and see my "personal" link at bottom.