Skip to content

Instantly share code, notes, and snippets.

@adeishs
Created November 7, 2022 11:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adeishs/0f606f32c07c8f4f7dabd3d16c4d8d90 to your computer and use it in GitHub Desktop.
Save adeishs/0f606f32c07c8f4f7dabd3d16c4d8d90 to your computer and use it in GitHub Desktop.
ANSI/ISO C89/C90 Tips

I have used C for my own software development tasks. I have also taught a university-level C course (well, this isn’t really relevant, but some think otherwise. Oh, well). Here are my tips based on my observation of common mistakes (including those I’ve done myself). I hope the following tips can reduce the occurrences of those mistakes.

  1. Be sure to fclose() after a successful fopen(). Similarly for *alloc() and free().

  2. An easy way to malloc():

    ptr = malloc(NUM * sizeof *ptr);
    

    This is good as we don’t need to care about the type of ptr. Note that ptr won’t be dereferenced since it’s used as an operand to sizeof, hence you don’t need to check whether its value is NULL.

  3. When you use command line arguments, be sure to check that they’re really there, for example:

    if (argc < 2)
    
  4. This way of using fread() is more generic and safer:

    fread(a, sizeof *a, sizeof a / sizeof *a, ...);
    

    compared to:

    fread(a, sizeof(char), MAX...);
    

    Why? Suppose you change the data type of a to, say, an array of doubles. If you use the latter form, you must still modify sizeof(char) to sizeof(double). As for the third parameter, you need to be careful, though. If a is an array, using sizeof a / sizeof *a always works. If a is a pointer (watch for this if you pass an array to a function, in which the array will decay into a pointer), you need to specify a number for the third parameter.

  5. Pedantically speaking, using such a construct is incorrect:

    while (!feof(fp)) {
        fread(...);
    }
    

    The following program will output the last line twice. Caution: try with a small file (unless you’re really keen waiting).

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define RECORD_LENGTH 50
    
    int main(int argc, char **argv)
    {
        char rec[RECORD_LENGTH + 1] = {0};
        FILE *dfp = NULL;
        int result = EXIT_SUCCESS;
    
        if (argc < 2) {
            fprintf(stderr, "%s datafile\n", argv[0]);
            result = EXIT_FAILURE;
        } else if (dfp = fopen(argv[1], "rb")) {
            while (!feof(dfp)) {
                fread(rec, sizeof *rec, sizeof rec - 1, dfp);
                printf("%s\n", rec);
            }
    
    /* compare with this:
    
            while (fread(rec, sizeof *rec, sizeof rec - 1, dfp)
                   > 0) {
                printf("%s\n", rec);
            }
    
    */
    
            fclose(dfp);
        } else {
            result = EXIT_FAILURE;
        }
    
        return result;
    }
    

    Further explanation is available.

  6. Don’t use // in C89. // is valid as of C99, though. It’s available in some C89 implementations as an extension, meaning that your code will lose some portability. If you want your program to be guaranteed to work with C89 implementations, /* */ is the safest.

  7. Use correct printf() format specifier. Otherwise, your program will invoke undefined behaviour. For example, if the argument is an int, use "%d".

  8. Use meaningful identifier/macro names.

    #define SEVENTY_THOUSANDS 70000
    

    is not useful. Use, for example:

    #define NUM_OF_RECORDS 70000
    
  9. Don’t use macros beginning with a “_” (underscore). This is reserved for C implementations.

  10. There’s no ‘method’ in C.

  11. Don’t put unused variables/functions/macros.

  12. Portable return values for main(): 0, EXIT_SUCCESS, and EXIT_FAILURE (the latter two are available by including <stdlib.h>). Other than those are implementation-defined/non-portable.

  13. Avoid unnecessary casting. There are times when casts are appropriate, which may include when using to*() and is*().

  14. How to use fread():

    #define REC_LENGTH 100
    
    char buf[REC_LENGTH];
    
    fread(buf, sizeof *buf, sizeof buf, fp);
    
  15. sizeof(char) is always 1. So, there’s no point writing any_value * sizeof(char). It always evaluates to any_value. If you have the habit of writing sizeof multiplied by some multiplier (and insist doing so), you’d better use sizeof object instead of sizeof(data_type).

  16. How to use fgets():

    #define MAX_LEN 1024
    
    char buf[MAX_LEN + 1];
    
    if (fgets(buf, sizeof buf, stdin)) {
        …
    }
    

    Of course, you can replace stdin with other input streams and 1024 with another valid positive integral value.

  17. Don’t use assert() on a condition you can’t control internally from your program. For example, don’t use it to enforce the number of command lines supplied to your program. Don’t use it to replace if. Use it to prevent undefined behaviours, or enforce a condition that should or should not be satisfied.

  18. sizeof returns size_t, not int, not long, not float, etc. That means, strictly speaking, the following code invokes undefined behaviour:

    int x;
    
    printf("%d\n", sizeof x);
    

    To fix this, use the following in C89:

    int x;
    
    printf("%lu\n", (unsigned long)sizeof x);
    

    or in C99/later standards:

    int x;
    
    printf("%zu\n", sizeof x);
    

    Similarly for many other functions that return size_t, such as strlen() and fread().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment