Starting point for a basic Hello World.
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Go and get fcuked\n");
}
| Type | bits | Range (typical) | Format | |
|---|---|---|---|---|
| char | 8 | -128 to 127 | %c | |
| unsigned char | 8 | 0 to 255 | %c | |
| int | 32 | -2,147,483,648 to 2,147,483,647 | %d | |
| unsigned int | 32 | 0 to 4,294,967,295 | %u | |
| short | 16 | -32,768 to 32,767 | %hd | |
| unsigned short | 16 | 0 to 65,535 | %hu | |
| long | 32 or 64 | ~±2.1 billion (32-bit) ~±9.2 quintillion (64-bit) | %ld | |
| unsigned long | 32 or 64 | 0 to 4.2 billion (32-bit) 0 to 18.4 quintillion (64-bit) | %lu | |
| long long | 64 | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 | %lld | (C99) |
| unsigned long long | 64 | 0 to 18,446,744,073,709,551,615 | %llu | (C99) |
| float | 32 | 6-7 digits ±1.2×10⁻³⁸ to ±3.4×10³⁸ | %f | |
| double | 64 | 15-16 digits ±2.3×10⁻³⁰⁸ to ±1.7×10³⁰⁸ | %lf | |
| long double | 64-128 | 18-21 digits ±3.4×10⁻⁴⁹³² to ±1.1×10⁴⁹³² | %Lf |
Boolean Type (C99)
| Type | Size | Values | Format | Description |
|---|---|---|---|---|
| _Bool | 1 byte | 0 or 1 | %d | Boolean type (C99) |
| bool | 1 byte | true/false | %d | With stdbool.h included |
Standard Library Types (Architecure-dependent size)
| Type | Header | Size | Format | Description |
|---|---|---|---|---|
| size_t | <stddef.h> <stdio.h> | 0 to SIZE_MAX | %zu | Unsigned size type for sizes and counts |
| ssize_t | <sys/types.h> | Negative to positive | %zd | Signed size type (POSIX) |
| ptrdiff_t | <stddef.h> | PTRDIFF_MIN to PTRDIFF_MAX | %td | Pointer difference type |
| intptr_t | <stdint.h> | Holds any pointer | %"PRIdPTR" | Integer type that can hold a pointer |
| uintptr_t | <stdint.h> | 0 to UINTPTR_MAX | %"PRIuPTR" Unsigned integer type for pointers | |
| off_t | <sys/types.h> | Varies | Large file offsets File offset type (POSIX) |
Fixed-Width Integer Types (C99)
| Type | Header | Size | Range | Format | Description |
|---|---|---|---|---|---|
| int8_t | <stdint.h> | 8 | -128 to 127 | %"PRIi8" | |
| uint8_t | <stdint.h> | 8 | 0 to 255 | %"PRIu8" | |
| int16_t | <stdint.h> | 16 | -32,768 to 32,767 | %"PRIi16" | |
| uint16_t | <stdint.h> | 16 | 0 to 65,535 | %"PRIu16" | |
| int32_t | <stdint.h> | 32 | -2.1×10⁹ to 2.1×10⁹ | %"PRIi32" | |
| uint32_t | <stdint.h> | 32 | 0 to 4.3×10⁹ | %"PRIu32" E | |
| int64_t | <stdint.h> | 64 | -9.2×10¹⁸ to 9.2×10¹⁸ | %"PRIi64" | |
| uint64_t | <stdint.h> | 64 | 0 to 1.8×10¹⁹ | %"PRIu64" |
// declare
int x = 21;
int y = 32, z = 14;
An array name acts as a pointer to the first element in the array.
This means that array indexing and pointer maths can be used interchangeably.
int numbers[5] = {1, 2, 3, 4, 5};
// Accessing elements using array indexing
printf("numbers[2] = %d\n", numbers[2]); // Output: 3
// Accessing elements using pointers
printf("*(numbers + 2) = %d\n", *(numbers + 2)); // Output: 3
// Pointer arithmetic
int *ptr = numbers;
printf("Pointer ptr points to numbers[0]: %d\n", *ptr); // Output: 1
ptr += 2;
printf("Pointer ptr points to numbers[2]: %d\n", *ptr); // Output: 3
return 0;
typedef struct Coordinate {
int x;
int y;
int z;
} coordinate_t;
coordinate_t points[3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};
printf("points[1].x = %d, points[1].y = %d, points[1].z = %d\n",
points[1].x, points[1].y, points[1].z
);
// points[1].x = 4, points[1].y = 5, points[1].z = 6
Arrays are basically pointers and the memory arrays use are contiguous in memory, so an array of one type can be recast to another type
This is struct that contains 3 ints and the struct array has 3 members. Therefore it actually contains 9 ints. The struct array can be recast as an int array and all numbers read that way.
typedef struct coordinate {
int x;
int y;
int z;
} coordinate_t;
coordinate_t points[3] = {
{5, 4, 1},
{7, 3, 2},
{9, 6, 8}
};
int *points_start = (int *)points;
for (int i = 0; i < 9; i++) {
printf("points_start[%d] = %d\n", i, points_start[i]);
}
/*
points_start[0] = 5
points_start[1] = 4
points_start[2] = 1
points_start[3] = 7
points_start[4] = 3
points_start[5] = 2
points_start[6] = 9
points_start[7] = 6
points_start[8] = 8
*/
An array will decay into a pointer in some cases.
When passed to a fucntion, an array decays to a pointer.
When used with the sizeof operator, it doesn't, but in the below example, if you use sizeof in a function, it only returns the size of a pointer as the array has already decayed getting to the pointer.
As the below code shows, in main(), you can get the size of an array, but when you pass it to a function, the array decays into a pointer, so getting the size of that array just returns the size of the pointer.
If a function needs the length of an array, this must be passed in seperately.
void core_utils_func(int core_utilization[]) {
printf("sizeof core_utilization in core_utils_func: %d\n", sizeof(core_utilization));
}
int main() {
int core_utilization[] = {43, 67, 89, 92, 71, 43, 56, 12};
int len = sizeof(core_utilization) / sizeof(core_utilization[0]);
printf("sizeof core_utilization in main: %zu\n", sizeof(core_utilization));
printf("len of core_utilization: %d\n", len);
core_utils_func(core_utilization);
return 0;
}
// sizeof core_utilization in main: 32
// len of core_utilization: 8
// sizeof core_utilization in core_utils_func: 8 (64-bit system)
When creating structs, as a rule of thumb, to make efficient use of memory, order the fields so that the largest ones are listed first. This will reduce the amount of padding the compiler will use.
You can use struct name {} or typedef struct {} name
The modern and recommended way is to use typedef
Example of create a date struct, then a person struct which imbeds the date struct, showing various ways of initialising data in that struct
You can use typedef to create a named or anonymous struct.
If it's names, you can still use struct name to create variables
With anonymous, you can't
// named struct
typedef struct numbers
{
int squared;
int cubed;
} numbers_t;
//anonymous struct
typedef struct
{
int squared;
int cubed;
} more_numbers_t;
int main(int argc, char **argv)
{
numbers_t num = {6,12};
struct numbers num2 = {7,21};
more_numbers_t num = {6,12};
struct more_numbers num2 = {7,21}; // this won't work
}
// create a struct called Date, and then a variable date of type Date
typedef struct Date
{
int day;
int month;
int year;
} date;
// anonymous struct which creates a variable called date
typedef struct
{
int day;
int month;
int year;
} date;
typedef struct
{
char name[50];
int id;
Date dob;
int level;
} Person;
int main()
{
Person craig = {
.name = "Craigus Hammondus",
.id = 69,
.dob.day = 14,
.dob.month = 8,
.dob.year = 1970,
.level = 1};
Person cocko = {"Cock Breath", 10, {14, 8, 1970}, 1};
Person dick;
strcpy(dick.name, "Dick Wad");
dick.id = 9;
dick.dob.day = 1;
dick.dob.month = 10;
dick.dob.year = 1969;
dick.level = 20;
struct City {
char *name;
float lat;
float lon;
};
// Zero initialiser. Sets all fields to 0 values.
struct City c = {0};
// Positional Initialiser
struct City c = {"Melbourne", -37.814, 144.963};
// Designated Initialiser
// This is usually preferred over positional as the positional code is more fragile if fields change
struct City c = {
.name = "Melbourne",
.lat = -37.814,
.lon = 144.963
};
A c function can only return 1 value, but that value can be a struct to effectively return as many values as you want.
struct numbers
{
int squared;
int cubed;
};
struct numbers goBigger(int num) {
struct numbers x = {0};
x.squared = num * num;
x.cubed = num * num * num;
return x;
}
// doing the same without creating a new variable
struct numbers goBigger2(int num) {
return (struct numbers){.squared = num*num, .cubed = num*num*num};
}
int main(int argc, char **argv)
{
int num = 5;
struct numbers n = goBigger(num);
printf("%d squared: %d\n",num,n.squared);
printf("%d cubed : %d\n",num,n.cubed);
}
Compilers will often pad out the fields in a struct for memory alignment.
This uses more memory, but results in faster memory access.
There are times when you care more about the exact memory used by structs, such as network file access.
struct __attribute__((__packed__)) mystruct {
int i;
char c;
};
Allows you to set a variable in a function that is scoped to that function, but lives for the life of the program, and so over multiple function calls.
void counter()
{
static int x = 0;
x++;
printf("counter: %d\n", x);
}
int main()
{
counter();
counter();
counter();
return 0;
}
// Output:
// counter: 1
// counter: 2
// counter: 3
// Display structer via pointers
Person *ptr = &craig;
// old method
printf("Name: %s\n", (*ptr).name);
// using arrow operator. (preferred)
printf("Name: %s, id: %d\n", ptr->name, ptr->id);
// Pass by Value
void printPerson(Person p) {
printf("Name: %s\n", s.name");
}
// Pass by reference using pointers
void setPersonId (Person *p, int newId) {
p->id = newId;
}
//Pointers to struct without the -> syntactic sugar
p->id
//becomes
(*p).id //have to use parentheses as . operator has higher precedence than *
Pointers not associated with a type are void pointers
Below is a int pointer, then showing the same thing with a void pointer.
With void pointers, as the type isn't known, you have to cast it to the desired type when accessing it.
int a;
int *ptr_a;
ptr_a = &a;
a = 5;
printf("The value of a is %d\n", a);
*ptr_a = 6;
printf("The value of a is %d\n", a);
//
// Using void pointers
//
int a;
void *ptr_a;
ptr_a = &a;
a = 5;
printf("The value of a is %d\n", a);
*(int *)ptr_a = 65;
printf("The value of a is %d\n", *((int *)ptr_a));
printf("The value of a as a char is %c\n", *((char *)ptr_a));
C will only return 1 value from a function.
To be able to get back more, you need to use pointers, where the function accepts pointers, and when calling the funtion, you pass the addresses of variables, passing by reference, so you can update those variables.
void numbers(int num1, int num2, int *diff, int *sum, int *mul)
{
*diff = num1 - num2;
*sum = num1 + num2;
*mul = num1 * num2;
}
int main()
{
int d = 0;
int s = 0;
int m = 0;
int num1 = 5;
int num2 = 4;
numbers(num1, num2, &d, &s, &m);
printf("Diff of %d & %d is: %d\n", num1, num2, d);
printf("Sum of %d & %d is: %d\n", num1, num2, s);
printf("Mul of %d & %d is: %d\n", num1, num2, m);
A double pointer is a pointer that points to anothe pointer
int i;
int *pi = &i;
int **ppi = π
It is particularly useful if you want to realloc memory in a seperate function.
When using realloc to get more memory, it's likely the starting address of that block of memory will change.
Normally for a function to update variables outside it's scope, you pass it a pointer, but now it's actually a pointer we want to update, we need a pointer to a pointer, or double pointer.
In the below example, it shows how you can have a function reallocate memory with and without double pointers. If you can use the return value of the function as the new pointer, you don't need double pointers.
If the return value of the function is returning that status of the function rather than the new memory pointer, you need to use double pointers.
#include <stdio.h>
#include <stdlib.h>
typedef enum
{
STATUS_GOOD,
STATUS_BAD,
} status_t;
// Using Double pointers
status_t foo(int **arr, size_t new_size)
{
int *temp = realloc(*arr, new_size * sizeof(int));
if (temp == NULL)
{
printf("Memory allocation failed\n");
return STATUS_BAD;
}
*arr = temp;
return STATUS_GOOD;
}
// Not using double pointers.
int *bar(int *arr, size_t new_size)
{
int *temp = realloc(arr, new_size * sizeof(int));
if (temp == NULL)
{
printf("Memory allocation failed\n");
return NULL;
}
return temp;
}
int main()
{
size_t initial_size = 5;
size_t new_size = 10;
size_t new_size2 = 20;
int *arr = malloc(initial_size * sizeof(int));
if (arr == NULL)
{
printf("Memory allocation failed\n");
return 1;
}
// Init the array
for (size_t i = 0; i < initial_size; i++)
{
arr[i] = i;
}
// Print the initial array
printf("Initial array:\n");
for (size_t i = 0; i < initial_size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
if (STATUS_BAD == foo(&arr, new_size))
{
printf("Memory re-allocation failed");
return -1;
}
// Init the resized array
printf("Resized array:\n");
for (size_t i = 0; i < new_size; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
int *temp;
// reallocate memory without double pointers.
// If as in this example you end the whole program if the realloc fails, the use of *temp
// isn't required. Use temp if you want to continue on after a failed realloc
temp = bar(arr, new_size2);
if (temp == NULL)
{
printf("Memory re-allocation failed");
return -1;
}
arr = temp;
// Init the resized array
printf("2nd Resized array:\n");
for (size_t i = 0; i < new_size2; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
& AND
^ XOR (Exclusive OR)
| OR (Inclusive)
~ One's complement
<< shift bits left
>> shift bits right
strings are just pointers to a char array, so you can't compare strings. If you tried, you'd just be comparing pointers, therefore memory addresses.
You also can't assign a string to another for the same reason you can't compare them. Strings are just pointers.
Not recommended as it's unsafe, but you can just use sprintf, which will also add the terminating zero at the end of the string.
char string[40];
sprintf(string, "You are is %d", age);
strcpy(str1, str2) copy str2 to str1
strcar(str1, str3) append str3 to str1 from the existing terminating 0
strcmp(str1, str2) compares them. returns 0 if they are the same
strncmp(str1, str2, 3) compares the first 3 letters of the two strings
strlen(str1) return the length of a string, not including the terminating 0
scanf is very basic and a bit to fragile for almost any situation
A basic way to get user input that is less fragile to user inputs and is also safe from buffer overflows is to use fgets() and sscanf()
#include <stdio.h>
#include <string.h>
int main() {
char buffer[256];
int number;
printf("Enter a number: ");
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
// Remove newline character
buffer[strcspn(buffer, "\n")] = '\0';
if (sscanf(buffer, "%d", &number) == 1) {
printf("You entered: %d\n", number);
} else {
printf("Invalid input!\n");
}
}
return 0;
}
To look for include file in the standard system directories
#include <stdio.h>
To look first in the current directory, ie a user defined header file.
#include "cocko.h"
Header files contain many things, including
Executable code normally goes into a source code file, not a header file
Setting definitiions.
Eg, you can define DEBUG code, then active it by either putting the #define DEBUG at the top, or compile it with the -DDEBUG flag to get the same result.
#define MAX_RECORDS 1024
#define DEBUG
// further in your code, you could have debug only code.
#ifdef DEBUG
printf("We are in DEBUG mode: %s:%d\n", __FILE__, __LINE__);
#endif
Super simple example on opening a file.
If you run this and it prints out fd, it will be 3
This is because all programs have 3 file descriptors already attached.
0 = stdin
1 = stdout
2 = stderr
#include <stdio.h>
#include <fcntl.h>
int main() {
int fd = open("./gagf.txt", O_RDONLY);
if (fd == -1 ) {
perror("OPEN");
return -1;
}
printf("%d\n", fd);
return 0;
}
Writing to a file
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv) {
if (argc != 2 ) {
printf("Usage: %s <filename>\n", argv[0]);
// return 1;
}
int fd = open(argv[1], O_RDWR | O_CREAT, 0644);
if (fd == -1 ) {
perror("open");
return -1;
}
char *mydata = "Hello File !!!!\n";
int err = write(fd, mydata, strlen(mydata));
if (err == -1) {
perror("write");
return -1;
}
close(fd);
return 0;
}
There are several input functions. scanf() is the most general of them and can read a variety of formats
Reads from stdin and scans according to the input format provided. %s - string, %d - decimal, %c - char, %f - float
When scanf reads in data to a basic variable, you pass it the address of the variable.
If you use scanf to read string into a character array, you just pass it the variable name without the & operator.
__func__
Returns the name of the function at the time of running
#define enter() printf("enter: %s\n", __func__)
#define leave() printf("leave: %s\n", __func__)
void foo()
{
enter();
// do stuff
leave();
}
void bar()
{
enter();
// do more stuff
leave();
}
int main()
{
foo();
printf("main\n");
bar();
}
Something I noticed only when using C23.
I think when I used the cland vscode extension
This is called inlay hints and can be turn on and off with cmd-shift P, and search for inlay
//Entering the following line
bytes = fread(buf, 1, BUF_SIZE, stdin);
// Would display as this:
bytes = fread(ptr:buf, size: 1, nitems: BUF_SIZE, stream: stdin);