This article is mainly notes I have taken for CSCB09/CSC209 at UofT.

Pointers to Pointers

You can have pointers to pointers, which is very common, let’s say changing original string.

void chage_str(char **src); // This will be a pointer to string, which is another pointer

Command Line Arguments

Command line arguemnts are passed to main using two parameters:

int main (int argc, char *argv[])

argc is the number of arguments, argv is a list of arguments in string, 0th element is the name of the program.

You can also do

int main (int argc, char **argv)

Reason is simple, I won’t go into detail here.

typedef

Remember when we did structs, you have to do struct student each time?

Use typedef to create a shorthand:

struct student {
    // Def here
};
typedef struct student Student;

Now you can simply use Student.

Another shorthand would be

typedef struct student {
    // Def here
} Student;

Program Organization

.h Files

.h files’ purpose is so the compiler knows how to use each function without look into function definations.

gcc

When you run gcc main.c list.c -o program

What happends are:

  • First it will look for all #include and includes the .h files.
  • Then compiler will compile each .c file, which will produce object code .o.
  • Finally the linker will combine all object files into one program.

Makefile

Makefiles are processed by a program called make.

The syntax is very simple:

target: dependencies.c
	gcc -Wall -o main dependencies.c # Commands

Makefile only accepts tab, do not use spaces!!!!

CC = gcc
CFLAGS = -Wall -g
buxfer: buxfer.o lists.o lists.h
	$(CC) $(CFLAGS) -o buxfer buxfer.o lists.o
buxfer.o: buxfer.c lists.h
	$(CC) $(CFLAGS) -c buxfer.c
lists.o: lists.c lists.h
	$(CC) $(CFLAGS) -c lists.c
clean:
	rm buxfer *.o

Pre-processer Directives

You can use some directives to make your life easier.

#define MAX 100 // Define macros
#ifndef LISTS_H // Conditionals
..
#endif

Error Handling

Include errno.h to do error handling.

By default, errno is zero, but once an error occurs, it is set.

You can use man errno for all possible values. For example: EACCESS.

perror

Use perror to print error message on screen.

if(/* bad things happen here */) {
    perror("ERROR");
    exit(1);
}

File I/O

We have already seen inputs and outputs, using printf.

But how about a file?

Coorisponding file functions are:

printf -> fprintf(FILE *stream, ...)
putchar -> fputc(int c, FILE *stream)
puts -> fputs(const char *s, FILE *stream)

There are two main machanisms for managing files

  • File descriptors, which is a small int.
  • File pointers, FILE * that contains a file descriptor and a buffer.

Standard Streams

All programs have three files open by default

FILE *stdin; // 0
FILE *stdout; // 1
FILE *stderr; // 2

If you do fprintf(stdout, ...), this is identical to printf.

Regular Files

If you want to operate on regular files, you must open them.

FILE *fopen(const char *filename, const char *mode);

mode can be r, w or a.

To close a file, use fclose(FILE *stream).

Reading From Files

To read from a file, use

char *fgets(char *s, int size, FILE *stream);

It will read until \n or size-1 characters.

#define MAX 50
// ...
char buf[MAX];
FILE *f = fopen("file.txt", "r");
while(fgets(buf, MAX, f) != NULL) {
    printf("%s", buf);
}
fclose(f);

Binary I/O

Use fread and fwrite to operate on a binary level.

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

Both will return number of items written/read.