Journal Articles

CVu Journal Vol 1, #2 - Dec 1987 + Programming Topics
Browse in : All > Journals > CVu > 012 (9)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Everything You Wanted To Know About C ......

Author: Martin Moene

Date: 20 June 2010 08:58:00 +01:00 or Sun, 20 June 2010 08:58:00 +01:00

Summary: Tips and Tricks from Steven Palmer

Body: 

The following tips and tricks were collected together after a study of some potential problems encountered by beginners to C. Space does not permit me to include many other, similiarily useful, tips. If you have any to contribute, please send them to the editor.

1. Constants

All integer literal constants are stored as integer, unless you supply the L suffix. Beware of calling a function that expects a argument of type long using a literal. For example

lseek(fh, 0, SEEK_BEG);

may not work because lseek() expects the second argument, the offset, to be a long integer. It should be re-written as

lseek(fh, 0L, SEEK_BEG);

All floating-point literal constants are stored as double, rather than just plain float. So the following

float count;
...
count = 8.95;

will elicit a 'Data Conversion' warning from some compilers because the 8.95 has type double which has to be converted down to float. If you feel picky about minor warnings, you can get round this by casting the literal

count = (float)8.95;

Again, beware of calling functions with literals if they are expecting an argument of type float. All mathematical libraries tend to use arguments of type double as a sort of safety catch. You should also #include <math.h> so that the compiler can catch and warn about unintended conversions.

2. Operator Precedence

You have probably come across the case where assignments inside a conditional, such as

while ((c = getchar()) != ' ')
have to be bracketed, otherwise they will be interpreted by the compiler as

while (c = (getchar() != ' '))
Take some time to learn the C operator precedence levels. There are some other catches for the unwary. For example

if (p & 0xC0 == 0xC0)
does NOT mean what it seems. The == operator has higher precedence than the &, so the expression must be re-written as

if ((p & 0xC0) == 0xC0)

A good C compiler should catch the former and warn the user about possible missing parenthesis.

3. #define macros

Except for simple macros, place the definition part of all #define preprocessor statements inside brackets. This avoids any ambiguity arising from apparently innocent usage such as the following

#define TABLE_BASE 0x2000
#define TABLE_LIMIT TABLE_BASE+0x1000
#define TABLE_SIZE TABLE_LIMIT-TABLE_BASE/sizeof(long)

TABLE_SIZE is definitely NOT set to the correct value. The macro will actually be interpreted as

0x2000+0x1000-0x2000/4

(assuming sizeof(long) is 4 bytes), or 0x2800. The second and third #defines must be rewritten as

#define TABLE_LIMIT (TABLE_BASE+0x1000)
#define TABLE_SIZE (TABLE_LIMIT-TABLE_BASE)/sizeof(long)

If the macro takes any arguments, always place the arguments inside the definition in brackets.

#define isdigit(c) ((c) >= '0' && (c) <= '9')

Finally, never call a macro with an expression that modifies itself, or any other part of the expression in that same macro. For example, the following are very suspect

isdigit(*c++)
isdigit(*c += 1)
isdigit(getchar())

4. Type declarations

Complex type declarations, while they are fun to explore and show off in listings, are one of the biggest headaches in C maintainability. Beware that if you make use of complex declarations, your code may be difficult to port to another language, such as Pascal or Ada. However if this is not too much of an issue, you can sometimes create very sophiscated and efficent data structures this way. For example

typedef char (* fchr)[4];
fchr (*routine)(char *);

declares a pointer to a function that returns a pointer to a character array of four elements. It can be assigned an address by

fchr lookup(char *);
...
routine = lookup;

and called by

fchr cptr;
cptr = (*routine)("HELP")

On return, cptr will point to the start of a 4-element character array, and can be used to skip over those characters to the start of the next 4 characters using

++cptr;

If your compiler allows you to examine the generated object code in assembler form, you can get some idea of how much more efficent the object code is over a more straightforward approach.

5. Debugging

If you are unlucky enough not to be using a proper C development system which includes a source-code debugger, like C-trace or Codeview, you will probably be wasting time tracking down obscure bugs that a source-code debugger would track down within minutes. However, there are a few tricks that you can use to help debug your program without having to restort to a machine-code debugger. Here are some...

(1) Use the serial or printer port to route diagnostic messages. If you have a spare terminal, you can connect it to the serial port of your computer and direct all diagnostics so that they appear on the terminal. You can also use a printer, but be careful of placing the diagnostics inside large loops ... you can end up wasting a lot of good printer paper!

(2) Use assertions. Assertions allow you to check that a specific condition is true before you execute the next statement. If your compiler does not support assertions, you can use the following. Place it in a file called "assert.h" in your header directory

#ifdef DEBUG
#define assert(n,e) {if (!(e)) { \ fprintf(stderr,"Assertion number %d failed!\n", (n)); \ exit(1); } }
#else #define assert(n,e) #endif

Use it like this

/* Switch on assertions */
#define DEBUG
#include <assert.h>
...
/* Check that x is non-zero */
assert(1,x != 0);
p = q/x;
...

If the assertion failed because x has the value 0, then the program will print the following and stop.

Assertion number 1 failed!

(3) Predefine all functions. By predefining functions, you are telling the compiler what types of arguments it expects and what type it returns. This

way, you will be warned if you accidently pass a floating-point value to a function that expects an integer, or try to use the result of a function that does not return a value. An example of a predefined function is

char *malloc(unsigned int);

This says that function malloc() takes one argument of type unsigned int, and returns a pointer to a character.

(4) LINT your source code, or use the highest possible warning level on your compiler. This ensures that the compiler will report on possible bugs that are normally ignored at the default warning level, on non-portable constructs or on wasteful code. LINT is a utility available under UNIX and some other operating systems that performs a very strict check of your source code.

(5) Use the C-Vu technical help section. If you are stuck with a really persistent bug, get in contact with either Martin Houston or Steven Palmer. If they can't help, then they'll do their best to find someone who can, or even contact the software house who developed your compiler to see if the bug could be a product of the compiler itself. That is what a user group is for!

Notes: 

More fields may be available via dynamicdata ..