#include <stdio.h> /* <- make sure the I/O functions are visible to the compiler */
/* main is the functions the OS calls when a program is loaded. It gets the
* number of command-line arguments and an array with the arguments. The first
* entry is the name of the executable.
*
* Main returns an integer that is the exit status of the program (0 on success).
*/
int main(int argc, char **argv) {
("Hello, World!\n"); /* We've used printf before */
printf
return 0;
}
Save as hello.c
and compile this program using
$ gcc hello.c -o hello
Note that we don’t need to use -no-pie
because we are not combining our C code with any assembly
Blocks of scope are delimited by {
and }
Functions are declared pretty much like Java methods:
(type1 arg1, type2 arg2, ...) return_type function_name
void
Control flow
We have the usual conditionals:
if (something) {
// do stuff
}
if (something) {
// do stuff
}
else {
// do other stuff
}
While loops and do-while loops
while (condition) {
// do this while condition holds
}
do {
// do this at leas once and then keep doing it again while condition holds
} while (condition);
For loops
for (initializer; condition; updater) {
// 1. run the initializer expression
// 2. if condition holds go to 3, else go to 6
// 3. do stuff in body
// 4. run the updater expression
// 5. Go to 2
// 6. End
}
Comparison operators: <
, >
, <=
, >=
, ==
Logical operators: !
, &&
, ||
You can skip the rest of the current iteration of the innermost loop with continue
You can break out of the innermost loop with break
Types
signed
and unsigned
integral types: char
(1 byte = 8-bit), short int
(16-bit), int
(32-bit), long int
(64-bit)unsigned short int
float
(32-bit) and double
(64-bit) (most implementations: IEE 754 single- and double-precision floats)struct
s (see below)union
s (later)12
, -10.5
, …'
'
: 'a'
"Hello, World"
(see below)No booleans: just 0
and non-0
_Bool
now…)0
is “false”!(!b) == b
does not hold as a resultNo string type, just a collection of characters
Using the *
after the type name
E.g.,
int *pi; // pi is a pointer to an integer (uninitialized)
Note: If a pointer isn’t set to point to anything right away, it’s good practice to initialize it to NULL
:
int *pi = NULL;
Getting the value that a pointer points to is called dereferencing and is denoted by putting *
before the variable name:
("%d\n", *pi); printf
The easiest way we can get a pointer to point to something meaningful is to give it an address
The address-of operator &
serves this purpose:
int i = 42;
int *pi = &i; // pi now points to where the variable i is stored
("%d %d\n", i, *pi); // 42 42
printf
= 43;
i ("%d %d\n", i, *pi); // 43 43
printf
*pi = 44; // note the *
("%d %d\n", i, *pi); // 44 44
printf
int j = 123;
= &j;
pi ("%d %d\n", i, *pi); // 44 123 printf
Pointers can point to other pointers:
int i = 42;
int *pi = &i;
int **ppi = &p;
("%d %d %d\n", i, *pi, **pi); printf
You can have more levels of indirection
Remember char **argv
?
Spoiler alert: arrays are just pointers with some fancy syntax
We will work with static (size known at compile-time) and dynamic arrays
We’ll talk about static ones first
E.g.,
float nums[4]; // create an array of 4 floats
This makes room for 6 floats
These will be stored contiguously in memory
nums
points to the first element
We can access them individually using indices, starting from 0
float nums[4]; // create an array of 4 floats
[0] = 0.1;
nums[1] = 3.14;
nums[2] = 1.5;
nums[3] = 3214;
nums
("2nd element: %f\n", nums[1]); printf
Arrays can also be initialized:
float nums[4] = { 0.1, 3.14, 1.5, 3214 };
("2nd element: %f\n", nums[1]); printf
In C (like in Assembly for us), strings are just arrays of characters, terminated by a 0 byte (also written '\0'
)
A string literal "Hello, world!"
is just the corresponding array of characters with an extra char for 0
I.e.
char msg1[6] = "Hello";
char msg2[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
// msg1 and msg2 define exactly the same object in memory
Memory can be allocated using the library function malloc
It’s defined in stdlib.h
It takes the number of bytes we want and returns a pointer to the block of memory (if successful)
Allocated memory needs to be freed using free
int *one_int = malloc(4);
*one_int = 42;
(one_int); free
We will mostly use malloc
to allocate arrays and struct
s (below)
int *fifty_ints = malloc(50 * sizeof(int));
for (int i = 0; i < 50; ++i) {
[i] = i * i;
fifty_ints}
(fifty_ints); free
Structs are the most useful user-defined data types in C
Think of them as Java classes, but everything is public (thought make the whole struct opaque) and there are no methods
They’re actually more similar to the define-struct
in Fundies I, but there are types
A struct allows us to store multiple values of different types together
It is defined using the struct
keyword:
struct address {
unsigned int house_no;
char street[32];
char city[24];
char state[3];
unsigned int zip;
}
Note that structs occupy a separate namespace - to create a struct variable of the above type, we need to use the struct keyword again:
struct address home, work; // inside a function this will allocate two
// structs on the stack
To access a field, we use the .
:
.house_no = 360;
work(work.street, "Huntington Ave"); // see man 3 strcpy
strcpy(work.city, "Boston");
strcpy(work.state, "MA");
strcpy.zip = 02115; work
Structs can, of course, be nested:
struct person {
char first[32];
char last[32];
struct address home;
}
They can be passed to and returned from a function:
struct address get_address(struct person p) { ... }
typedef
Writing out struct every time can be tiring
C allows us to introduce type synonyms using typedef
:
typedef person_t struct person; // now we can use person_t to mean struct person
typedef
can be used with any type to make code more readable:
typedef age_t unsigned char;
Of course, we can have pointers to structs:
struct person *p; // OR
*p; person_t
We can use the address-of operator &
to get the address of a struct:
struct address *current = &work;
We can also allocate memory for structs dynamically, using malloc
and sizeof
:
struct person *ferd = malloc(sizeof(struct person)); // or equivalently:
*ferd = malloc(sizeof(person_t)); person_t
We can also create arrays of structs:
[80];
person_t class*friends = malloc(5 * sizeof(person_t));
person_t
// ...
for (int i = 0; i < 5; ++i) {
if (strcmp(friends[i].home.street, "Huntington Ave") == 0) {
("%s lives close!\n", friends[i].first);
printf}
}
Often, pointers are used to pass a struct to a function, to avoid copying the contents into the function’s stack frame
When accessing fields via a pointer, we can use ->
to make things more readable:
int lives_in_boston(person_t *p) {
return strcmp(p->home.city, "Boston") != 0; // equivalent to
return strcmp((*p).home.city, "Boston") != 0;
}
#define
#include
#ifdef
/#ifndef
…
…
…