A computer is a physical machine.
#include <stdio.h>
int main(void) {
printf("Hello World\n");
return 0;
}
void
$ gcc hello.c
$ ./a.out
Hello World
$
Newline must be explicit
Python
C
Standard implementation of Python - “CPython” - is written in C.
int increment(int num) {
return num + 1;
}
compiles to (https://godbolt.org)
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
add eax, 1
pop rbp
ret
Basic Types
Modifiers for integer types
Exact sizes and ranges somewhat platform dependent :(
Because type corresponds to memory size, be careful to stay within bounds:
(low + high) / 2
may give gibberishlow + (high - low) / 2
is safea char is an integer but written as a single quoted character
'0'
is same as 48 (ASCII value)'0'
is conventional and preferred'\n'
'\0'
is null character (equal to 0)strings are denoted by double quotes
'\0'
at the end"a"
is not same as 'a'
!enum for autoincrementing constants
#include <stdio.h>
int main(void) {
enum fruits { APPLE, BERRY, CHERRY };
printf("%d %d %d\n", APPLE, BERRY, CHERRY);
return 0;
}
$ gcc enum.c
$ ./a.out
0 1 2
(You can also explicitly assign values.)
Variables must be declared (and initialized) before use.
int x, y;
char msg[] = "hello";
const double pi = 3.1415;
const: value can’t change (compiler can enforce)
Global variables
Local variables
Arithmetic Operators
+
, -
, *
, /
, %
/
is integer divisionRelational Operators
>
, >=
, <
, <=
==
, !=
Logical Operators
&&
(and), ||
(or), !
(not)Boolean expression
!
turns 0 to 1, and nonzero to 0compound expressions separated by &&
/||
not true in general
f(x) + g(x)
, f or g may be evaluated firstUsually, C automatically casts type
1 + 1.0
is a double floatsqrt(2)
worksTo explicitly cast:
(type) expression
e.g., (float) 5 / 2
evaluates to 2.5
#include <stdio.h>
int main(void) {
char x = 'a';
printf("%c\n", ++x);
printf("%c\n", x);
printf("%c\n", x++);
printf("%c\n", x);
return 0;
}
$ gcc prefix_postfix.c
$ ./a.out
b
b
b
c
$
Often idiomatic (but confusing) to increment within expression.
#include <stdio.h>
int main(void) {
int i = 0, ones[3];
while (i < 3) {
ones[i++] = 1; // ++i does not work
}
for (i = 0; i < 3; i++) {
printf("%d: %d\n", i, ones[i]);
}
return 0;
}
$ gcc ones.c
$ ./a.out
0: 1
1: 1
2: 1
$
Assignment Operators
x = x + 3
same as x += 3
Assignment Expressions
printf("%d\n", x = 42)
outputs 42while ((char = getchar()) != EOF)
if (x=1)
always trueGeneral Syntax
if (expression) statement else if (expression) statement ... else statement
else if is not a separate keyword like elif. The if simply begins another conditional.
switch (expression) { case const-expr: statements case const-expr: statements ... default: statements }
#include <stdio.h>
int main(void) {
int n = 2;
switch (n) {
case 1:
printf("one\n");
case 2:
printf("two\n");
case 3:
printf("three\n");
break;
case 4:
printf("four\n");
}
return 0;
}
$ gcc case.c
$ ./a.out
two
three
$
while loop runs as long as expression is nonzero
while (expression) statement
for loop
for (expr1; expr2; expr3) statement
is equivalent to
expr1; while (expr2) { statement expr3 }
for loop is idiomatic for iterating thru array
for (i = 0; i < n; i++) {
...
}
for(;;){ ... }
is an infinte loop!do-while loop
do statement while (expression);
do-while is relatively uncommon
Same as Python! Within a loop, break immediately exits the loop, and continue goes back to the top of the loop.
You can label code and jump to a label.
#include <stdio.h>
int main(void) {
goto yay;
printf("hello\n");
yay:
printf("world\n");
return 0;
}
$ gcc goto.c
$ ./a.out
world
$
“Although we are not dogmatic about the matter, it does seem that goto statements should be used rarely, if at all.”
– K&R
return-type name(parameters) { declarations and statements }
C program is a bunch of variable declarations and functions. Source code can be split among multiple files.
#include <stdio.h>
int main(void) {
double half(double);
printf("%f\n", half(5));
return 0;
}
double half(double x) { return x / 2; }
$ gcc half_main.c half.c
$ ./a.out
2.500000
$
The function prototype double half(double);
is necessary, otherwise the
compiler seeing a function used for the first time assumes it has
return type int.
This the main purpose of header (.h) files - get all the prototypes and other global declarations out of the way.
Where can a variable be used?
#include <stdio.h>
int counter() {
static int n = 0;
++n;
return n;
}
int main(void) {
printf("%d\n", counter());
printf("%d\n", counter());
printf("%d\n", counter());
return 0;
}
$ gcc counter.c
$ ./a.out
1
2
3
$
Variable can be local to any block, not just function block.
#include <stdio.h>
int main(void) {
int x = 2;
if (1) {
int x = 3;
printf("%d\n", x);
}
printf("%d\n", x);
return 0;
}
$ gcc block_scope.c
$ ./a.out
3
2
$
Probably should exploit this sparingly...
Array can be initialized with a literal
int nums[] = {1, 2, 3};
Strings are arrays of characters
char message[] = "hello";
is short for
char message[] = {'h', 'e', 'l', 'l', 'o', '\0'};
Yes.
#include <stdio.h>
int fib(int n) {
if (n <= 2)
return 1;
return fib(n - 1) + fib(n - 2);
}
int main(void) {
int n = 1;
while (n <= 10) {
printf("%d ", fib(n));
n++;
}
printf("\n");
}
$ gcc fib.c
$ ./a.out
1 1 2 3 5 8 13 21 34 55
$
C processor manipulates souce code before compilation.
#include injects content of another file
#include <stdio.h>
adds standard IO library headers#include "my_headers.h"
adds file from current directory#define creates substitution rule
#define MAX 1000
substitutes 1000 wherever MAX occurs#ifndef and #endif often wrap a header file
#ifndef MY_HEADER_H #define MY_HEADER_H header content goes here #endif
A pointer is a variable whose value is the address of another variable.
#include <stdio.h>
int main(void) {
int x;
int *p;
p = &x;
x = 42;
printf("address %p contains %d\n", p, *p);
return 0;
}
$ gcc pointer.c
$ ./a.out
address 0x7ffee1317858 contains 42
$
*
*
operator also gives pointed-at value (dereferencing)&
operator gives address of a variablevoid *p;
declares pointer to nothing (yet)Because C functions pass by value, pointers allow “in-place” changes.
#include <stdio.h>
void triple(int *p) {
*p = *p * 3;
}
int main(void) {
int x = 5;
triple(&x);
printf("%d\n", x);
return 0;
}
$ gcc triple.c
$ ./a.out
15
$
Arrays and pointers are suprisingly close.
#include <stdio.h>
int main(void) {
int arr[] = {5, 4, 3, 2, 1};
int *p;
p = &arr[0];
printf("%d\n", *(p + 2));
p = arr;
printf("%d\n", *(p + 2));
printf("%d\n", *(arr + 2));
printf("%d\n", p[2]);
return 0;
}
$ $ gcc pointer_and_array.c
3
3
3
3
$
Because addresses are equally sized, can freely cast pointers
#include <stdio.h>
int main(void) {
unsigned long n = 256 * 256;
unsigned char *p = (unsigned char *)&n;
int i;
for (i = 0; i < sizeof n; i++) {
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
$ gcc little_endian.c
$ ./a.out
0 0 1 0 0 0 0 0
$
In most computers, integer digits from small to large (“little-endian”).
When passing array name to function, it is the address that is passed.
#include <stdio.h>
void print_array(int *p, int len) {
int i;
for (i = 0; i < len; ++i)
printf("%d ", p[i]);
printf("\n");
}
int main(void) {
int arr[] = {5, 4, 3, 2, 1};
print_array(arr, 5);
return 0;
}
$ gcc print_array.c
$ ./a.out
5 4 3 2 1
$
This is known as “array decay”.
Since strings are arrays, functions that take a string receive pointer.
#include <stdio.h>
void string_copy(char *source, char *target) {
while ((*source++ = *target++))
;
}
int main(void) {
char original[] = "hello";
char copy[6];
string_copy(copy, original);
printf("%s\n", copy);
return 0;
}
$ gcc string_copy.c
$ ./a.out
hello
$
Command line arguments can be passed to main function with the signature
int main(int argc, char *argv[])
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
static int acc = 1;
if (argc < 2) {
printf("error: need argument\n");
return 0;
}
int n = atoi(argv[1]);
if (n <= 1) {
printf("%d\n", acc);
return 1;
} else {
acc = n * acc;
sprintf(argv[1], "%d", --n);
return main(argc, argv);
}
}
$ gcc factorial.c
$ ./a.out 5
120
$
Since a function is in memory, pointer can point to it
#include <stdio.h>
int add_two(int n) { return n + 2; }
int triple(int n) { return n * 3; }
int main(void) {
int (*fp)(int);
fp = &add_two;
printf("%d\n", (*fp)(42));
fp = triple;
printf("%d\n", fp(42));
return 0;
}
& and * are optional, but clarifying
$ gcc function_pointer.c
$ ./a.out
44
126
$
With arrays, pointers, and functions, declarations can get complex
char ( * ( *x () ) [ ] ) ()
???Structures are basically classes but without methods
#include <stdio.h>
int main(void) {
struct cat {
char *name;
char *greeting;
int hunger;
} grumpy;
grumpy.name = "Grumpy";
grumpy.greeting = "Meh";
grumpy.hunger = 80;
printf("%s, I am %s. My hunger is %d.\n",
grumpy.greeting, grumpy.name, grumpy.hunger);
struct cat bub = {"Lil Bub", "Meow", 50};
printf("%s, I am %s. My hunger is %d.\n",
bub.greeting, bub.name, bub.hunger);
return 0;
}
$ gcc cat.c
$ ./a.out
Meh, I am Grumpy. My hunger is 80.
Meow, I am Lil Bub. My hunger is 50.
$
cat
is (optional) structure tag, a short hand for { ... }Like other types, structure are passed by value. May be more efficient to pass pointer to structure.
#include <stdio.h>
struct cat {
char *name;
char *greeting;
int hunger;
};
void speak(struct cat *cp) {
printf("%s, I am %s. My hunger is %d.\n",
(*cp).greeting, cp->name, cp->hunger);
}
int main(void) {
struct cat grumpy = {"Grumpy", "Meh", 80};
struct cat *cp = &grumpy;
speak(cp);
return 0;
}
$ gcc pointer_cat.c
* ./a.out
Meh, I am Grumpy. My hunger is 80.
$
A member of a structure can be pointer to another structure (or itself), you can build a larger data structure out of them.
You can alias a type by using typedef
#include <stdio.h>
typedef struct cat {
char *name;
char *greeting;
int hunger;
} Cat;
void speak(Cat *cp) {
printf("%s, I am %s. My hunger is %d.\n",
(*cp).greeting, cp->name, cp->hunger);
}
int main(void) {
Cat grumpy = {"Grumpy", "Meh", 80};
Cat *cp = &grumpy;
speak(cp);
return 0;
}
$ gcc typedef_cat.c
$ ./a.out
Meh, I am Grumpy. My hunger is 80.
$
Input / output not part of C language but handled by library functions, mostly in stdio.h.
Formatted output with printf
int printf(char *format, ...)
printf returns count of characters printed
common format options:
printf is a variadic function. Use <stddarg.h> library to implement your own.
Formatted input with scanf
int scanf(char *format, ...)
scanf returns number of assigned items. Arguments must be pointers!
To access files, use the FILE type (a structure)
$ gcc open_file.c $ ./a.out #include <stdio.h> int main(void) { FILE *fp = fopen("open_file.c", "r"); int c; while ((c = getc(fp)) != EOF) { putc(c, stdout); } printf("n"); fclose(fp); return 0; } $
The compiler allocates memory for variables, but often more memory is required during execution. For example, instantiate an object in Python.
C provides the malloc function to dynamically allocate memory.
#include <stdio.h>
#include <stdlib.h>
typedef struct cat {
char *name;
char *greeting;
int hunger;
} Cat;
void speak(Cat *cp) {
printf("%s, I am %s. My hunger is %d.\n",
cp->greeting, cp->name, cp->hunger);
}
int main(void) {
int i; for (i = 0; i < 3; i++) {
Cat *cp;
cp = (Cat *)malloc(sizeof(Cat));
cp->name = "Grumpy"; cp->greeting = "Meh";
cp->hunger = 15;
speak(cp);
free(cp);
}
return 0;
}
$ gcc malloc_cat.c
$ ./a.out
Meh, I am Grumpy. My hunger is 15.
Meh, I am Grumpy. My hunger is 15.
Meh, I am Grumpy. My hunger is 15.
$
#include <stdio.h>
#include <unistd.h>
int main(void) {
char buffer[BUFSIZ];
int n;
while ((n = read(0, buffer, BUFSIZ)) > 0)
write(1, buffer, n);
return 0;
}
$ gcc readwrite.c
$ echo "hello" | ./a.out
hello
$
Higher level I/O functions such as printf ultimately make system calls, which are implemented by OS.
How do malloc and free work? Here’s one possible way:
See K&R (8.7) for detailed implementation. It is a masterpiece.