Beej's Guide to C Programming

hello world

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

int main(void)
{
    printf("Hello, World!\n");  // Actually do the work here
}
```adety6s]7804
\r\]=

## variables and statements

- variable is a human-readable name that refers to some data in memory.
- With the comma operator, the value of the comma expression is the value of the rightmost expression

```c
i = 10;
j = 5 + i++;  // Compute 5 + i, _then_ increment i

printf("%d, %d\n", i, j);  // Prints 11, 15
//
i = 10;
j = 5 + ++i;  // Increment i, _then_ compute 5 + i

printf("%d, %d\n", i, j);  // Prints 11, 16

x = (1, 2, 3);

printf("x is %d\n", x);  // Prints 3, because 3 is rightmost in the comma list

int a = 999;

// %zu is the format specifier for type size_t

printf("%zu\n", sizeof(a));      // Prints 4 on my system
printf("%zu\n", sizeof(2 + 7)); // Prints 4 on my system
printf("%zu\n", sizeof(3.14));   // Prints 8 on my system

more types

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
char a = 10, b = 'B';
print("%d\n", a+b);

int a = 0x1A2B;
int b = 012;
int c = 0b101010;
int x = 1234;
long int x = 1234L;
long long int x = 1234LL; // L, LL, U, UL, ULL
float x = 3.14f; // f, L(long double)
double x = 123.45632

conversions

  • numeric value to string
    • sprintf
    • snprintf
  • string to numeric value
    • atoi/atof/atol/atoll
    • strtol/strtoll/…/strtold (error handling)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
char s[10];
float f = 3.14159f;

snprintf(s, 10, "%f", f);

char *p = "3.14159";
float f = atof(p);

// print error message
char *s = "34x9";
char* bad;

unsigned long int x = strtoul(s, &bad, 10);
printf("%lu\n", x);

printf("invalid character: %c\n", *bad); // "x"

type qualifiers

  • const (constant)
  • volatile
  • auto
  • static
    • static in block scope - will only be initialized once on program startup, not each time the function is called
    • static in file scope - the variable is global, but only in this file
  • extern - refer variables in other files
  • register - to hint to the compiler that this variable is frequently-used
  • _Thread_local
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const int x = 10;

const int *p;  // Can't modify what p points to
int const *p;  // Can't modify what p points to, just like the previous line

const int const *p; // can't modify p or *p

int x = 10;
int *const p = &x;   // We can't modify "p" with pointer arithmetic
p++;  // Compiler error!
*p = 20; // Set "x" to 20, no problem

int a;
auto int a;

void counter(void)
{
    static int count = 1;  // This is initialized one time

    printf("This has been called %d time(s)\n", count);

    count++;
}

extern int a;

int main(void)
{
    register int a; // make a as fast to use as possible
    counter();  // "This has been called 1 time(s)"
    counter();  // "This has been called 2 time(s)"
    counter();  // "This has been called 3 time(s)"
    counter();  // "This has been called 4 time(s)"
}

functions

A parameter is a special type of local variable into which the arguments are copied.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

void increment(int a)
{
    a++;
}

int main(void)
{
    int i = 10;

    increment(i);

    printf("i == %d\n", i);  // What does this print? -> 10
}

With a prototype definitely use void when you have an empty parameter list.

1
2
3
// two different definition
void foo();
void foo(void);

pointers

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#include <stdio.h>

int main(void)
{
    int i;
    int *p;  // this is NOT a dereference--this is a type "int*"

    p = &i;  // p now points to i, p holds address of i

    i = 10;  // i is now 10
    *p = 20; // the thing p points to (namely i!) is now 20!!

    printf("i is %d\n", i);   // prints "20"
    printf("i is %d\n", *p);  // "20"! dereference-p is the same as i!
}

arithmetic

adding one to the pointer moves to the next item of that type directly after it in memory.

a[b] == *(a+b)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int a[5] = {11, 22, 33, 44, 55};

int *p = &a[0];  // Or "int *p = a;" works just as well
printf("%d\n", *(p + 1));  // Prints 22!!

while (*p != 999) {       // While the thing p points to isn't 999
    printf("%d\n", *p);   // Print it
    p++;                  // Move p to point to the next int!
}

int my_strlen(char *s)
{
    // Start scanning from the beginning of the string
    char *p = s;

    // Scan until we find the NUL character
    while (*p != '\0')
        p++;

    // Return the difference in pointers
    return p - s;
}

int main(void)
{
    printf("%d\n", my_strlen("Hello, world!"));  // Prints "13"
}

void

  • a function is going to operate on something byte to byte. memcpy
  • another function is calling a function you passed to it (callback), and it’s passing your data.
1
2
3
4
5
6
void *memcpy(void *s1, void *s2, size_t n);

int a[] = {11, 22, 33};
int b[3];

memcpy(b, a, 3 * sizeof(int));  // Copy 3 ints of data
  • cannot do point arithmetic on a void*
  • cannot de-reference a void*
  • cannot use the arrow operator on a void*
  • cannot use array notation on a void*

containers

  • fixed length array
  • string and the termination ‘\0’ to determine the length
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>

int main(void)
{
    int i;
    float f[4];  // Declare an array of 4 floats

    f[0] = 3.14159;  // Indexing starts at 0, of course.
    f[1] = 1.41421;
    f[2] = 1.61803;
    f[3] = 2.71828;

    // Print them all out:

    for (i = 0; i < 4; i++) {
        printf("%f\n", f[i]);
    }
}

// array length
int x[12];  // 12 ints

printf("%zu\n", sizeof x);     // 48 total bytes
printf("%zu\n", sizeof(int));  // 4 bytes per int

printf("%zu\n", sizeof x / sizeof(int));  // 48/4 = 12 ints!

// pointers and copy | that's why we need another param to pass length
void foo(int x[12])
{
    printf("%zu\n", sizeof x);     // 8?! What happened to 48?
    printf("%zu\n", sizeof(int));  // 4 bytes per int

    printf("%zu\n", sizeof x / sizeof(int));  // 8/4 = 2 ints?? WRONG.
}

// initialize
int a[5] = {22, 37, 3490};
// is the same as:
int a[5] = {22, 37, 3490, 0, 0};
int a[2][5] = {      // Initialize a 2D array
    {0, 1, 2, 3, 4},
    {5, 6, 7, 8, 9}
};
int a[3][3] = {[0][0]=1, [1][1]=1, [2][2]=1};

// pointer == arrays ?
int a[5] = {11, 22, 33, 44, 55};
int *p;

p = &a[0];  // p points to the array | Well, to the first element, actually

printf("%d\n", *p);  // Prints "11"

p = a; // p points to the array, but much nicer-looking!

// multiple dimension
void print_2D_array(int a[2][3])
void print_2D_array(int a[][3]) // The compiler really only needs the second dimension so it can figure out how far in memory to skip for each increment of the first dimension.
1
2
3
4
5
6
7
int my_strlen(char *s)
{
    int count = 0;
    while (s[count] != '\0')  // Single quotes for single char
        count++;
    return count;
}

structs

struct is often defined at the global scope outside any functions so that the struct is globally available.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct car {
  char *name;
  float price;
  int speed;
};

// the full type name is `struct car`
struct car saturn = {"Saturn SL/2", 16000.99, 175};

// initialize with specific member
struct car saturn = {.speed=175, .name="Saturn SL/2"};

void set_price(struct car *c, float new_price) {
    // (*c).price = new_price;  // Works, but non-idiomatic :(
    //
    // The line above is 100% equivalent to the one below:
    c->price = new_price;  // That's the one!
}

initialization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <stdio.h>

struct passenger {
    char *name;
    int covid_vaccinated; // Boolean
};

#define MAX_PASSENGERS 8

struct spaceship {
    char *manufacturer;
    struct passenger passenger[MAX_PASSENGERS];
};

int main(void)
{
    struct spaceship s = {
        .manufacturer="General Products",
        .passenger = {
            // Initialize a field at a time
            [0].name = "Gridley, Lewis",
            [0].covid_vaccinated = 0,

            // Or all at once
            [7] = {.name="Brown, Teela", .covid_vaccinated=1},
        }
    };

    printf("Passengers for %s ship:\n", s.manufacturer);

    for (int i = 0; i < MAX_PASSENGERS; i++)
        if (s.passenger[i].name != NULL)
            printf("    %s (%svaccinated)\n",
                s.passenger[i].name,
                s.passenger[i].covid_vaccinated? "": "not ");
}

File IO

This FILE* holds all the information needed to communicate with the I/O subsystem about which file you have open, where you are in the file, and so on. (stdin, stdout, stderr)

  • read
    • fgetc, fgets, fscanf
    • fread (binary)
    • EOF, NULL
  • write
    • fputc, fputs, fprintf
    • fwrite (binary)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
printf("Hello, world!\n");
fprintf(stdout, "Hello, world!\n");  // printf to a file

#include <stdio.h>

// read
int main(void)
{
  FILE *fp;                      // Variable to represent open file

  fp = fopen("hello.txt", "r");  // Open file for reading

  int c = fgetc(fp);             // Read a single character
  printf("%c\n", c);             // Print char to stdout

  // read all chars until EOF
  int c;
  while ((c = fgetc(fp)) != EOF)
  printf("%c", c);

  // read line
  char s[1024];
  int linecount = 0;
  while (fgets(s, sizeof s, fp) != NULL)
      printf("%d: %s", ++linecount, s);

  // formatted input
  char name[1024];
  float length;
  int mass;
  while (fscanf(fp, "%s %f %d", name, &length, &mass) != EOF) {
    printf("%s whale, %d tonnes, %.1f meters\n", name, mass, length);
  }

  fclose(fp);                    // Close the file when done
}

// write
int main(void)
{
  FILE *fp;
  int x = 32;

  fp = fopen("output.txt", "w");

  fputc('B', fp);
  fputc('\n', fp);   // newline
  fprintf(fp, "x = %d\n", x);
  fputs("Hello, world!\n", fp);

  fclose(fp);
}

// write binary
int main(void)
{
  FILE *fp;
  unsigned char bytes[6] = {5, 37, 0, 88, 255, 12};

  fp = fopen("output.bin", "wb");  // wb mode for "write binary"!

  // In the call to fwrite, the arguments are:
  //
  // * Pointer to data to write
  // * Size of each "piece" of data
  // * Count of each "piece" of data
  // * FILE*

  fwrite(bytes, sizeof(char), 6, fp);

  fclose(fp);
}

typedef

making an alias for the existing type with typedef.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
typedef int abc;

abc x = 10;

// practical usage
typedef struct animal {
  char *name;
  int leg_count, speed;

} animal;

struct animal x;
animal y;

typedef int *intptr;

int a = 10;
intptr x = &a;  // "intptr" is type "int*"

manual memory allocation

  • alloc/dealloc - malloc, free
  • error checking
  • calloc(num, size), also clears the memory to zero
  • realloc changing allocated space
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int *p = malloc(sizeof(int));
*p = 12;
printf("%d\n", *p);
free(p);

int *x;
if ((x = malloc(sizeof(int) * 10)) == NULL) {
  printf("error allocating 10 ints\n");
}

// calloc
int *p = calloc(10, sizeof(int));

// realloc
float *p = calloc(20, sizeof(*p));
float *new_p = realloc(p, sizeof(*p) * 40);
if (new_p == NULL) {
  printf("error\n");
  // the original memory block remains unchanged
  free(p);
  return 1;
}
p = new_p;
free(p);

// equivalent operation
char *p = malloc(3490);
char *p = realloc(NULL, 3490);

multifile projects

gcc -o foo foo.c bar.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ------ bar.h
// to resolve possible include cycle
#ifndef BAR_H
#define BAR_H

int add(int x, int y);

#endif

// ----- bar.c
int add(int x, int y)
{
    return x + y;
}

//---- foo.c
#include <stdio.h>

#include "bar.h"  // Include from current directory

int main(void)
{
    printf("%d\n", add(2, 3));  // 5!
}

the trick for fast building. (build c files seperately, then combine them; when modifying any single file, don’t need to rebuild the project)

1
2
3
4
gcc -c foo.c # produces foo.o
gcc -c bar.c # produces bar.o

gcc -o foo.o bar.o

command line arguments

1
2
3
4
5
6
7
8
#include <stdio.h>

// argument count & value
int main(int argc, char* argv[]) {
  for (int i = 0; i < argc; i++) {
    printf("arg %d: %s\n", i, argv[i]); // --> ./foo i like turtles
  }
}

exit status

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    if (argc != 3) {
        printf("usage: mult x y\n");
        return EXIT_FAILURE;   // Indicate to shell that it didn't work
    }

    printf("%d\n", atoi(argv[1]) * atoi(argv[2]));

    return 0;  // same as EXIT_SUCCESS, everything was good.
}

/// --- sh
$ ./mult 3 4 5
usage: mult x y
// to check the return status
$ echo $?
1

$ ./mult 3 4
12
$ echo $?
0

environment variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <stdlib.h>

extern char **environ;  // MUST be extern AND named "environ"

// int main(int argc, char **argv, char **env)  // <-- env!
int main(void)
{
    char *val = getenv("FROTZ");  // Try to get the value

    // Check to make sure it exists
    if (val == NULL) {
        printf("Cannot find the FROTZ environment variable\n");
        return EXIT_FAILURE;
    }

    printf("Value: %s\n", val);

    for (char **p = environ; *p != NULL; p++) {
        printf("%s\n", *p);
    }
}

pre-processor

  • include – to include other sources in your source
    • <> look in system-wide include directory
    • "" from current directory
  • simple macros
    • #define HELLO "hello world"
    • #define PI 3.1415926
  • conditional
    • #ifdef, #endif, #ifndef, #else, #elifdef, #elifndef, #if, #elif
    • #undef
  • built in marcos
    • __DATE__ the date of compilation Mmm dd yyyy
    • __TIME__ the time of compilation hh:mm:ss
    • __FILE__ a string containing the file’s name
    • __LINE__ the line number of the file this macro appears on
    • __func__ the name of the function this appears in, as a string
    • __STDC__ defined with 1 if this is a standard c compiler
    • __STDC_HOSTED__
    • __STDC_VERSION__
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>

#define EXTRA_HAPPY

int main(void) {
  #ifdef EXTRA_HAPPY
    printf("I'm very happy!\n");
  #endif
  printf("OK!\n");

  #ifdef EXTRA_HAPPY
      printf("I'm extra happy!\n");
  #else
      printf("I'm just regular\n");
  #endif

  printf("This function: %s\n", __func__);
  printf("This file: %s\n", __FILE__);
  printf("This line: %d\n", __LINE__);
  printf("Compiled on: %s %s\n", __DATE__, __TIME__);
  printf("C Version: %ld\n", __STDC_VERSION__);
}

sample output

1
2
3
4
5
This function: main
This file: foo.c
This line: 7
Compiled on: Nov 23 2020 17:16:27
C Version: 201710

macros with argument

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#define SQR(x) x*x ==> better version
#define SQR_v2(x) ((x) *(x))
#define TRIANGLE_AREA(w, h) (0.5 * (w) * (h))

// macros with variable arguments
#define X(a, b, ...) (10*(a) + 20*(b)), __VA_ARGS__

printf("%d\n", SQR(12));  // 144

printf("%d\n", SQR(3 + 4));  // 19!!??
printf("%d\n", 3 + 4 * 3 + 4);  // 19!

// stringification
#define STR(X) #x

printf("%s\n", STR(3.14)); // --> printf(.., "3.14");

// concatenation
#define CAT(a, b) a ## b
printf("%f\n", CAT(3.14, 1592));   // 3.141592

multiline macros

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <stdio.h>

#define PRINT_NUMS_TO_PRODUCT(a, b) do { \
    int product = (a) * (b); \
    for (int i = 0; i < product; i++) { \
        printf("%d\n", i); \
    } \
} while(0)

int main(void)
{
    PRINT_NUMS_TO_PRODUCT(2, 4);  // Outputs numbers from 0 to 7
}

references

Licensed under CC BY-NC-SA 4.0
Get Things Done
Built with Hugo
Theme Stack designed by Jimmy