Satan's Syntax

Published November 17, 2000 by Steve Goodwin, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement
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)!


[size="5"]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.


[size="5"]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.


[size="5"]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!


[size="5"]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.
Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!

A lot of us picked up bad habits from our earlier days of coding that are still with us today. This article discusses how we can use some quirks of C's syntax to overcome these habits and write more efficient code.

Advertisement
Advertisement