C is one of the most important and widely used of all programming languages. It is a powerful language that can be used not only to build general-purpose applications but also to write “low-level” programs that interact very closely with the computer hardware.
C lets the programmer do things that many other languages do not. This means that good C programmers are able to write clever, efficient code. However, there is a downside: while many other languages, such as C# or Java, may try to protect you from writing dangerous code that could crash your programs, C often lets you write just about any code you want-even allowing you to code in mistakes that will end in disaster. In some ways, developing in C is the programming equivalent of a high-wire act-without a safety net.
Experienced C programmers have all kinds of tricks to make the most of the C language. Here is a list of the top 10 tips for both new and experienced C programmers.
(Note – the source code is provided in a downloadable archive if you’d like to try them out.)
The code examples are supplied read-to-run for the NetBeans IDE. If you want to use them with another IDE, just import the code files or simply copy and paste the source code into an existing file.
1. FUNCTION POINTERS
Sometimes it is useful to store a function in a variable. This isn’t a technique that is normally used in day-to-day programming, but it can be used to increase the modularity of a program by, for example, storing the function to be used in handling an event in the event’s data (or control) structure.
The key here is to define a type, “pointer-to function-returning-something” and then use that as a variable declaration-it makes the code a lot easier to read. Let’s consider a simple example. First I define a type PFC, which is a Pointer to a Function returning a Character:
This is then used to create a variable z:
I define a function a():
The address of this function is then stored in z:
Note that you don’t need the & (“address-of”) operator here; the compiler knows that a must be the address of a function. This is because there are only two things you can do with a function: 1) call it, or 2) take its address. Since the function isn’t called (there are no parentheses after a in the assignment above), the only option is to use the address of the function, which is then stored in the variable z.
To call the function whose address is in z, just add the parentheses:
2. VARIABLE-LENGTH ARGUMENT LISTS
Normally you declare a function to take a fixed number of arguments. But it is also possible to define functions capable of taking variable numbers of arguments. The standard C function printf() is a function of this sort. You can put almost any number of integers, floats, doubles, and strings in the format specifier part (after the string argument), and the printf() function will figure out what to do with them. Just like printf(), you can declare your own functions that contain a variable number of arguments.
Here is an example:
The first argument here, arg_count, is an integer that gives the actual number of arguments that follow it in the “variable” argument list, which is shown by the three dots.
There are a few built-in functions or macros that deal with variable arguments: va_list, va_start,va_arg, and va_end (these are defined in the stdarg.h header file).
First, you need to declare a pointer to the variable arguments:
Next, set this argp variable to the first argument in the variable part. This is the argument after the last fixed argument; here arg_count:
Then we can extract each variable argument one at a time from the variable part using va_arg:
Note that you need to know in advance the type of the argument being retrieved (here it’s a simpleint) and the number of arguments (here, given by the fixed argument arg_count).
Finally, you need to tidy up by calling va_end:
3. TESTING AND SETTING INDIVIDUAL BITS
“Bit-twiddling”, or manipulating the individual bits of items such as integers, is sometimes considered to be a dark art used by advanced programmers. It’s true that setting individual bit values can seem a rather obscure procedure. But it can be useful, and it is a technique that is well worth knowing.
Let’s first discuss why you would want to do this. Programs often use “flag” variables to hold Boolean values (that is, true or false). So you might have a number of variables like these:
If these are related in some way, as the ones above are (they all define the state of some action related to movement), then it’s often more convenient to store all the information in a single “state variable” and use a single bit in that variable for each possible state, like this:
Then you can use bit-setting operations to set or clear an individual bit:
The advantage is that all the state information is stored in one place and it’s clear that you are operating on a single logical entity.
The code archive contains an example that shows how to set, clear, and test a single bit in a integer. If you don’t understand exactly what is going on here, don’t worry. The best way to think of these are as standard “incantations.”
To set a given bit in an int called value (in the range 0 to 31), use this expression:
To clear a given bit, use this:
And to test if a bit is zero or one, use this:
4. SHORT CIRCUIT OPERATORS
C’s logical operators, && (“and”) and || (“or”), let you chain together conditions when you want to take some action only when all of a set of conditions are true (&&) or when any one set of conditions is true (||). But C also provides the & and | operators. And it is vital that you understand the difference in how these work. In brief, the double-character operators (&& and ||) are called “short-circuit” operators. When used between two expressions, the second expression is only evaluated when the first expression is found to be true; otherwise it is skipped. Let’s look at an example to clarify this:
The test (int)f && feof(f) is intended to return a true value when the end of the file f is reached. This test evaluates f first; and this will be zero (a false value) if the file has not been opened. This is an error, so trying to read to the end of the file is not possible. However, since the first part of the test fails, the second part will not be evaluated, so no attempt is made on feof() . This shows the correct use of a short circuit operator to test if a file had been opened before an operation on the file is tried. But, see this code:
Here, the test uses the & operator instead of &&. The & operator is an instruction to evaluate both expressions in all circumstances. So, even if the first part of the test fails (as the file hasn’t been opened), the second part will be evaluated (to test for the end of the file). This could be disastrous and might cause a segmentation fault (or similar) because there is no control over the order of evaluation.
In fact, the way in which these expressions are evaluated is, to some extent, dependent on the compiler and optimizer. So, it is possible that some compilers might be smart enough to realize that this code can never succeed and therefore might not evaluate both parts of the test. Many compilers are not this smart, however; so a test of this sort in a C program, where the evaluation of one part is dependent on the other to be true, is a very bad idea!
5. TERNARY OPERATORS
A ternary operation is one that takes three arguments. In C the ternary operator (? 🙂 can be used as a shorthand way of performing if..else tests. The syntax can be expressed like this:
For example, given two int variables, t and items I could use if..else to test the value of items and assign its value to t like this:
Using the ternary operator, I could rewrite that entire code in a single line, like this:
If you aren’t used to them, ternary operators may look a bit odd, but they can shorten and simplify your code.
Here’s another example. This code displays the first string when there is a single item and the second string when there are multiple items:
This can be rewritten as follows:
6. STACKS – PUSHING AND POPPING
A “stack” is a last-in, first-out storage system. You can use address arithmetic to add elements to a stack (pushing) or remove elements from the stack (popping). When programmers refer to “the stack”, they typically mean the structure that is used by the C compiler to store local variables declared inside a function. But, in fact, a stack is a generic type of data structure that you can create and use in your own code, which is what I discuss here.
The code below defines a very small stack: an array _stack of 2 integers. Remember, when testing, it is always better to use small numbers of items rather than large numbers. If your code contains errors, these will be easier to spot in an array of two items rather than in array of 100 items. I also declare a stack pointer _sp and set it to the base (the address) of the _stack array:
I now define the push() function, which pushes an integer onto the stack, just as you might add a plate onto a stack of plates. It returns the new number of items on the stack, or -1 if the stack is full:
In order to get an item from the stack, I need a pop() function. Remember a stack is a last-in, first-out structure. If I have stacked up ten plates to be washed, I pull the first plate off the top of the stack (which was the last plate I put on the stack), wash it, and then take off the next plate (the last-but-one plate that I put on the stack) and so on. My pop() function does this with the elements stored in my_stack data structure. It returns the new number of items on the stack, or -1 if it is empty:
And here is some code showing how to push and pop items onto and off the stack:
Stacks are handy, temporary storage structures. It’s worth getting to know them!
7. COPYING DATA
Here are three ways of copying data. The first uses the standard C function, memcpy(), which copies n bytes from the src to the dst buffer:
Now let’s look at a do-it-yourself alternative to memcpy(). This could be useful if you wanted to do some more processing or checking of the copied data:
And finally, here is a function that uses 32-bit integers to achieve faster copying. Bear in mind that this may not be faster than the compiler can achieve if it makes optimizations that are particular to the machine architecture. However, it can be useful in a microcontroller where speed is often very important. In this particular example, the code assumes that the data count n is a multiple of 4 since it is dealing with 4-byte integers:
You can find some examples of copying strings using these three functions in the code archive.
8. TESTING FOR HEADER INCLUSION
C uses “header” (“.h”) files that may contain declarations of functions and constants. A header file may be included in a C code file by importing it using its name between angle brackets when it is one of the headers supplied with your compiler (#include < string.h >) or between double-quotes when it is a header that you have written: (#include “mystring.h”). But in a complex program containing many source code files, there is the danger that you may include the same header file more than once.
Suppose we have a simple header file, header1.h, that contains the following definitions:
Then we make another header (header2.h) that contains this:
Now, if in our main program, main.c, we have this:
When we compile the program, we will get a compilation error, because T_SIZE will be declared twice (because its definition in header1 is included in two different files). We have to include header1 in header2 in order to get header2 to compile in circumstances where we don’t use header1. So, how can we fix this problem? The way around this is to define a “guard” macro that encloses all of the definitions in a header file, so that header1 becomes:
This sort of problem is so common that many IDEs such as NetBeans will do this for you when you create a new header. If you create the header file yourself, however, you need to do this explicitly. To avoid this sort of error, you must make sure that all your header definitions are within the “guard” #ifdef.
9. PARENTHESES – TO USE OR NOT TO USE?
A competent and experienced C programmer will neither overuse nor underuse parentheses-the round bracket delimiters “(” and “)”. But what exactly is the correct way to use parentheses?
There are a number of simple rules:
1) To change the normal operator precedence.
For example, 3 * (4 + 3) is not the same as 3 * 4 + 3 .
2) To make things clearer. It isn’t absolutely necessary to use parentheses here:
That’s because the operator precedence of || is lower than < and >. However, you might find it clearer to write this:
Using parentheses for clarity is useful because not many people can correctly list all the C operator priorities.
3) In a macro expression. It is a good idea to add parentheses when defining a constant like this:
That’s because you don’t know where this constant might be used. In the example above, if there were no parentheses, you may not get what you expect. Consider this:
The resulting value would be different (due to the effects of operator precedence) if you omitted the parentheses in the constant declaration.
But there’s one place where you don’t need to use parentheses: in a return statement. For example, this…
…has exactly the same effect as
Many programmers make a habit of using unnecessary parentheses in return statements. This may be because they are used to placing expressions between parentheses in other control statements such as if, while, for, and do. All of those statements require parentheses. But a return statement does not.
10. ARRAYS AS ADDRESSES
Programmers who come to C from another language frequently get confused when C treats an array as an address and vice versa. After all, isn’t an array supposed to be some sort of container with a fixed number of slots, each of which holds a single item? An address is just a number indicating a memory location; so an array and an address are very different things, right?
Well, no, not right, as it turns out.
Look carefully at the following code:
Here, the first for loop copies the address of each individual array element into the array itself:
At each turn through the loop, the address is incremented by the value of i. So the address of the array variable _x will be the first element (since i is 0 at the first turn through the loop), and each subsequent address will be the address of _x plus 1. When we add 1 to the array’s address, the C compiler calculates the appropriate offset to the next array element according to the data-type of the array (here, that’s 4 bytes for an array of integers).
The second for loop prints the values stored in the array, first printing the address of the element _x + i, then the value of the element using normal array indexing _x[i], and finally the contents of the array using pointer/address notation (where the * operator returns the contents of the address placed in parentheses): *(_x + i). In all cases, the three values are the same. This shows that the array and its address are the same thing, and each element in the array has an address given by the address of the array, plus the number of bytes needed to store an element.
Incidentally, note that you don’t need to use the & operator to get the address of the array, because, to the compiler, the array is an address.
You can download the source code here if you’d like to try these tips on your own.