Skip to content

Instantly share code, notes, and snippets.

@peterwwillis
Last active September 13, 2020 12:58
Show Gist options
  • Save peterwwillis/53cd9d34d8755784e48379047af9f358 to your computer and use it in GitHub Desktop.
Save peterwwillis/53cd9d34d8755784e48379047af9f358 to your computer and use it in GitHub Desktop.
C Tips and Best Practices

Tips and Best Practices for programming in C

Syntax

Tips

  • You can explain a complicated declaration in English using cdelc.

Data Structures / Types

Type Qualifiers

  • const will qualify a given type as a constant value (it cannot change, or in other words, it is not a variable).
    • You can make a pointer constant, a non-pointer data type constant, and a constant pointer to a constant data type (a "double const"). You probably want to default to the double const, as this tells the compiler that both the pointer, and the value it is pointing at, should not be modified.
      • #include <stdint.h>
        int8_t bob = 42;                   // a variable
        int8_t const bob_c = 42;           // a constant data type
        int8_t * dobbs_1 = &bob;           // a regular pointer; both pointer and data can change
        int8_t const * dobbs_2 = &bob;     // a constant pointer to a variable; the 
        int8_t const * const dobbs = &bob; // a constant pointer to a constant data type
      You can read declarations backwards/spirally to make sense of this.

Tips

  • Do not use int, char, short, long, unsigned. Use Fixed-width types whenever possible.
    • Use #include <stdint.h> in your code
    • Use a specific integer width type like int8_t foo;. This is more portable than hoping your compiler and CPU architecture give you what you were expecting for int.
    • If you only need a minimum integer width, use the fast variants, like int_fast8_t. These may be larger than expected but will be faster for your platform.
    • Don't use char if you're just trying to do random unsigned byte manipulations; use uint8_t.
  • Booleans: For C99 and later, include #include <stdbool.h> and bool my_var = true; or bool my_var = false;

Pointers

Tips

  • Using pointers:

    char * const p;                // Technically this only needs to be 'char *p;', but by making it constant, it
                                   // can only be assigned once, avoiding an accidental reassignment later.
    
    p = malloc(6 * sizeof(char));  // The ' * sizeof(char)' can be avoided here because 'sizeof(char)' is always 1,
                                   // but I include it anyway just to be complete.
    *p = 'H';
    *(p + (1 * sizeof(char))  ) = 'e';
    *(p + (2 * sizeof(char))  ) = 'l';
    *(p + (3 * sizeof(char))  ) = 'l';
    *(p + (4 * sizeof(char))  ) = 'o';
    *(p + (5 * sizeof(char))  ) = '\0';
    printf("%s\n", p);
    free(p);
    • The p is the name of the pointer.
    • The memory it points to (a char) will be accessed with *p.
    • *p is really only the start of the data pointed to. Its value is equivalent to a char p. *(p + (1 * sizeof(char)) ) is just another char p that happens to lie directly after the first one in memory.

    Another example:

    char *p = malloc(10);

    In this case, we're declaring a char pointer, and then using malloc as an initializer for the pointer named p.

  • Using pointers to pointers:

     char *foo;
     void *vfoo1;
     void **vfoo2;
     char *text = "This is some text.";
     char *text2 = "This is the new text.";
    
     // Allocate memory into 'foo' and copy 'text' into it.
     // Don't do 'foo = &text;' because 'text' is static (it can't be modified later)
     foo = strdup(text);
     printf("\nfoo '%s'\n", foo);
    
     vfoo1 = foo;
     vfoo2 = &foo;
    
     printf("vfoo1 '%s'\n", (char *) vfoo1);
     printf("vfoo2 '%s'\n", (char *) *vfoo2);
    
     // Write directly into first array element of 'foo'
     (**(char **)vfoo2) = 'C';
     printf("vfoo2 '%s'\n", (char *) *vfoo2);
    
     // see that the memory foo points to has changed
     printf("foo '%s'\n", foo);
    
     *vfoo2 = strdup(text2);
     printf("foo '%s'\n", foo);

    Output:

    foo 'This is some text.'
    vfoo1 'This is some text.'
    vfoo2 'This is some text.'
    vfoo2 'Chis is some text.'
    foo 'Chis is some text.'
    foo 'This is the new text.'
    

Memory Management

Q&A

  • Q: "Is it possible that somehow after I malloc, the malloc'd pointer gets assigned to some trash memory? Should the programmer ensure this does not happen? Otherwise free will try to free trash memory and probably crash."
  • A: Just use a const pointer! You won't be able to change the pointer after this, though. You can take advantage of local scope to avoid this becoming a problem.
    int * const p = malloc(sizeof(int));
    if(p==NULL)
    {
       /* do some error handling. either 'sizeof(int)' was 0, or malloc failed */
    }
    /* do what you want with p, but you won't be able to change its value */
    free(p);
    You don't need to initialize p as NULL, since malloc() returns NULL if an error occured. You don't need to check if p is NULL, free() will check that for you.

Tips

  • If you use malloc, always #include <stdlib.h>.
    • If you don't, you will get casting errors, and the compiler will assume malloc returns an int.

GCC

Tips

  • To print GCC header search dirs: gcc -print-search-dirs

Links

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