Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
98 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

A lot of people code by example. This example becomes a template. Once the template is known, different variables, different values, and different functions are applied to the template and combined with glue code to implement the required solution. By being more aware of the syntax, much of this glue can be removed. This article illustrates a few quirks of the C syntax, and how it can be used (abused?) to implement more efficient code, without qualifying for the IOCCC (International Obfuscated C Code Contest)!

Remember The Return Types

My first example of this "template programming" will involve the formatting output function sprintf. It is not unusual to write code such as:

sprintf(str1, "Old v=%d\t",v); /* Some code that plays with v */ sprintf(str2, "New v=%d",v); strcat(str1, str2); printf(str1);

Most instances of sprintf use a temporary string as the first parameter. This is the template that becomes engrained: "sprintf requires a temporary string". Whereas a better template would be a syntactical one, "sprintf requires a pointer to a character [array]". This is a reminder that we could, instead, use a function that returns a char * as our first parameter, saving a temporary buffer. For example:

sprintf(str, "Old v=%d\t",v); /* Some code that plays with v */ sprintf(strchr(str, '\0'), "New=%d",v); printf(str);

You should be prudent in using pointers directly, to avoid deferencing a NULL. In this case, strchr(str, '\0') is always valid thanks to the first sprintf. There is also a hidden bonus with sprintf - it returns the number of characters written into the buffer - which can save a call to strlen! Check your libraries. You might get lucky!

To take another example, if the reason for getting a string is to determine its length is valid for some operation, you might write:

len = strlen(GetFileName()); if (len > 0) ; /* File name is not null */

However, if all you're going to do is check len against zero, why not write:

if (GetFileName()[0] != '\0') ; /* File name is not null */

This time, not only do we save ourselves another temporary buffer (which is quite common, seeing as we use the result directly - it also eliminates the extraneous copy that usually follows), but we also show understanding for the data being returned (that a NULL-length string starts with '\0'), and we also remove the strlen overhead. Note that the same result could be achieved with the equivalent code:

if (*GetFileName () != '\0') ; /* File name is not null */

As a string is simply a structure (of type char *), you could also use this method with functions that return whole structures, C++ classes or pointers to them, and just isolate the element you want. For example:

struct POINT GetCurrentPos(void); int y; y = GetCurrentPos().y;

or,

printf(GetDevice()->pName);

The compiler is likely to create the structures as temporary variables, and pass their pointers as hidden parameters, so you need not worry about the overhead of copying structures to and from the stack.

Just as a side point, remember the lessons that say "you can't return a string from a function"? Well. You can. Of sorts. By creating a structure of type STRING consisting of one character array called str, for instance, you can return STRING from a function, referencing it in the same manner.

struct STRING { char str[256]; }; struct STRING GetName(void) { . } printf(GetName().str);

In the real world, this method offers little or no benefit to passing the string in as a pointer, but it does save the programmer creating temporary variables.

Expressions

Function calls are just expressions. If they return a type (excluding void, which isn't really a type) then you can use them anywhere you would use a normal expression, like a while loop. This allows entire loops and the like to be short-circuited quickly, taking advantage of C's lazy evaluator (see sidebar).

while (1 && GetNextLine(&str)) ;

Changing the 1 to a 0 causes the whole expression to evaluate to 0: The GetNextLine function does not get called, and the loop never executes. I often use this method to remove long winded if statements from experimental code:

if (1 && ComplicatedExpression1 && ComplicatedExpression2 && ComplicatedExpression3 ) ;

By making the 1 a global, or static, variable, I can then remove the code during program execution with the debugger, or change it with a special menu option, allowing me to test different section on a single compile.

End

I hope there's a few new ideas for you there. I neglected to mention code could also be removed with

/* . code here . //*/

and re-inserted by adding a single / to the first line. But that's just too nasty!

Sidebar

C uses a lazy evaluator. This means it will only evaluate the necessary expressions to prove the expression is definitely TRUE, or definitely FALSE. So a line like,

if (fn1() && fn2() && fn3()) .

Would evaluate fn1() first, but would only continue onto fn2() if it had too. Namely, if fn1() returned TRUE. If it returned FALSE, there is nothing the other functions could do to make the final expression TRUE. Most people know this instinctively from code snippets like:

if (ptr && ptr->Name) printf(ptr->Name);

This is different from Pascal.

Click here for a printable version of this article.


After graduating with honours from Loughborough University, where he wrote his first compiler, Steven Goodwin took a temporary job writing computer games. Many years later, he's still doing it, finding more ways to manipulate the syntax than his colleagues would like! His computing interests include esoteric languages, code optimisation and automatic MIDI compositions. His interests outside of computing also use computers.