This commit is contained in:
Chris Harding 2024-10-29 18:10:33 +05:30 committed by GitHub
commit 19ff753258
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -11,6 +11,7 @@ contributors:
- ["Joshua Li", "https://github.com/JoshuaRLi"] - ["Joshua Li", "https://github.com/JoshuaRLi"]
- ["Dragos B. Chirila", "https://github.com/dchirila"] - ["Dragos B. Chirila", "https://github.com/dchirila"]
- ["Heitor P. de Bittencourt", "https://github.com/heitorPB/"] - ["Heitor P. de Bittencourt", "https://github.com/heitorPB/"]
- ["Chris Harding", "https://github.com/sjrct"]
--- ---
Ah, C. Still **the** language of modern high-performance computing. Ah, C. Still **the** language of modern high-performance computing.
@ -27,7 +28,8 @@ memory management and C will take you as far as you need to go.
> >
> `-Wall -Wextra -Werror -O2 -std=c99 -pedantic` > `-Wall -Wextra -Werror -O2 -std=c99 -pedantic`
> >
> For information on what these flags do as well as other flags, consult the man page for your C compiler (e.g. `man 1 gcc`) or just search online. > For information on what these flags do as well as other flags, consult the man
> page for your C compiler (e.g. `man 1 gcc`) or just search online.
```c ```c
// Single-line comments start with // - only available in C99 and later. // Single-line comments start with // - only available in C99 and later.
@ -63,26 +65,28 @@ enum days {SUN = 1, MON, TUE, WED = 99, THU, FRI, SAT};
// libraries for the headers. // libraries for the headers.
// For your own headers, use double quotes instead of angle brackets, and // For your own headers, use double quotes instead of angle brackets, and
// provide the path: // provide the path:
#include "my_header.h" // local file #include "my_header.h" // local file
#include "../my_lib/my_lib_header.h" //relative path #include "../my_lib/my_lib_header.h" //relative path
// Declare function signatures in advance in a .h file, or at the top of // Declare function signatures in advance in a .h file, or at the top of
// your .c file. // your .c file.
void function_1(); void function_1(void);
int function_2(void); int function_2(int a, float b);
// At a minimum, you must declare a 'function prototype' before its use in any function. // At a minimum, you must declare a 'function prototype' before its use in any
// Normally, prototypes are placed at the top of a file before any function definition. // function. Normally, prototypes are placed at the top of a file before any
// function definition.
int add_two_ints(int x1, int x2); // function prototype int add_two_ints(int x1, int x2); // function prototype
// although `int add_two_ints(int, int);` is also valid (no need to name the args), // although `int add_two_ints(int, int);` is also valid (no need to name the
// it is recommended to name arguments in the prototype as well for easier inspection // args), it is recommended to name arguments in the prototype as well for
// easier inspection
// Function prototypes are not necessary if the function definition comes before // Function prototypes are not necessary if the function definition comes before
// any other function that calls that function. However, it's standard practice to // any other function that calls that function. However, it's standard practice
// always add the function prototype to a header file (*.h) and then #include that // to always add the function prototype to a header file (*.h) and then #include
// file at the top. This prevents any issues where a function might be called // that file at the top. This prevents any issues where a function might be
// before the compiler knows of its existence, while also giving the developer a // called before the compiler knows of its existence, while also giving the
// clean header file to share with the rest of the project. // developer a clean header file to share with the rest of the project.
// Your program's entry point is a function called "main". The return type can // Your program's entry point is a function called "main". The return type can
// be anything, however most operating systems expect a return type of `int` for // be anything, however most operating systems expect a return type of `int` for
@ -125,7 +129,8 @@ int main (int argc, char** argv)
short x_short = 0; short x_short = 0;
// chars are defined as the smallest addressable unit for a processor. // chars are defined as the smallest addressable unit for a processor.
// This is usually 1 byte, but for some systems it can be more (ex. for TMS320 from TI it's 2 bytes). // This is usually 1 byte, but for some systems it can be more
// (ex. for TMS320 from TI it's 2 bytes).
char x_char = 0; char x_char = 0;
char y_char = 'y'; // Char literals are quoted with '' char y_char = 'y'; // Char literals are quoted with ''
@ -153,8 +158,8 @@ int main (int argc, char** argv)
// sizeof(obj) yields the size of the expression (variable, literal, etc.). // sizeof(obj) yields the size of the expression (variable, literal, etc.).
printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words) printf("%zu\n", sizeof(int)); // => 4 (on most machines with 4-byte words)
// If the argument of the `sizeof` operator is an expression, then its argument // If the argument of the `sizeof` operator is an expression, then its
// is not evaluated (except VLAs (see below)). // argument is not evaluated (except VLAs (see below)).
// The value it yields in this case is a compile-time constant. // The value it yields in this case is a compile-time constant.
int a = 1; int a = 1;
// size_t is an unsigned integer type of at least 2 bytes used to represent // size_t is an unsigned integer type of at least 2 bytes used to represent
@ -171,15 +176,15 @@ int main (int argc, char** argv)
// You can initialize an array of twenty ints that all equal 0 thusly: // You can initialize an array of twenty ints that all equal 0 thusly:
int my_array[20] = {0}; int my_array[20] = {0};
// where the "{0}" part is called an "array initializer". // where the "{0}" part is called an "array initializer".
// All elements (if any) past the ones in the initializer are initialized to 0: // Elements (if any) past the ones in the initializer are initialized to 0:
int my_array[5] = {1, 2}; int my_array[5] = {1, 2};
// So my_array now has five elements, all but the first two of which are 0: // So my_array now has five elements, all but the first two of which are 0:
// [1, 2, 0, 0, 0] // [1, 2, 0, 0, 0]
// NOTE that you get away without explicitly declaring the size // NOTE that you get away without explicitly declaring the size of the
// of the array IF you initialize the array on the same line: // array IF you initialize the array on the same line:
int my_array[] = {0}; int my_array[] = {0};
// NOTE that, when not declaring the size, the size of the array is the number // NOTE that, when not declaring the size, the array's size is the number of
// of elements in the initializer. With "{0}", my_array is now of size one: [0] // elements in the initializer. With "{0}", my_array is now of size one: [0]
// To evaluate the size of the array at run-time, divide its byte size by the // To evaluate the size of the array at run-time, divide its byte size by the
// byte size of its element type: // byte size of its element type:
size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]); size_t my_array_size = sizeof(my_array) / sizeof(my_array[0]);
@ -249,7 +254,7 @@ int main (int argc, char** argv)
i2 * i1; // => 2 i2 * i1; // => 2
i1 / i2; // => 0 (0.5, but truncated towards 0) i1 / i2; // => 0 (0.5, but truncated towards 0)
// You need to cast at least one integer to float to get a floating-point result // You need to cast at least one int to float to get a floating-point result
(float)i1 / i2; // => 0.5f (float)i1 / i2; // => 0.5f
i1 / (double)i2; // => 0.5 // Same with double i1 / (double)i2; // => 0.5 // Same with double
f1 / f2; // => 0.5, plus or minus epsilon f1 / f2; // => 0.5, plus or minus epsilon
@ -366,13 +371,17 @@ int main (int argc, char** argv)
printf("\n"); printf("\n");
// *****NOTES*****: // *****NOTES*****:
// Loops and Functions MUST have a body. If no body is needed: // If the body of control expression is only one statement then parentheses
// are optional, but recommended:
for (jj = 0; jj < 5; jj++)
printf("%d\n", jj);
// Loops and if statements MUST have a body. If no body is needed:
int i; int i;
for (i = 0; i <= 5; i++) { for (i = 0; i <= 5; i++) {
; // use semicolon to act as the body (null statement)
} }
// Or // Or
for (i = 0; i <= 5; i++); for (i = 0; i <= 5; i++); // semicolon acts as the body (null statement)
// branching with multiple choices: switch() // branching with multiple choices: switch()
switch (a) { switch (a) {
@ -441,8 +450,9 @@ int main (int argc, char** argv)
// without warning. // without warning.
printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long) printf("%d\n", (unsigned char) 257); // => 1 (Max char = 255 if char is 8 bits long)
// For determining the max value of a `char`, a `signed char` and an `unsigned char`, // For determining the max value of a `char`, a `signed char` and an
// respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX macros from <limits.h> // `unsigned char`, respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX
// macros from <limits.h>
// Integral types can be cast to floating-point types, and vice-versa. // Integral types can be cast to floating-point types, and vice-versa.
printf("%f\n", (double) 100); // %f always formats a double... printf("%f\n", (double) 100); // %f always formats a double...
@ -453,9 +463,9 @@ int main (int argc, char** argv)
// Pointers // Pointers
/////////////////////////////////////// ///////////////////////////////////////
// A pointer is a variable declared to store a memory address. Its declaration will // A pointer is a variable declared to store a memory address. Its declaration
// also tell you the type of data it points to. You can retrieve the memory address // will also tell you the type of data it points to. You can retrieve the
// of your variables, then mess with them. // memory address of your variables, then mess with them.
int x = 0; int x = 0;
printf("%p\n", (void *)&x); // Use & to retrieve the address of a variable printf("%p\n", (void *)&x); // Use & to retrieve the address of a variable
@ -493,9 +503,9 @@ int main (int argc, char** argv)
int* x_ptr = x_array; int* x_ptr = x_array;
// x_ptr now points to the first element in the array (the integer 20). // x_ptr now points to the first element in the array (the integer 20).
// This works because arrays often decay into pointers to their first element. // This works because arrays often decay into pointers to their first element.
// For example, when an array is passed to a function or is assigned to a pointer, // For example, when an array is passed to a function or is assigned to a
// it decays into (implicitly converted to) a pointer. // pointer, it decays into (implicitly converted to) a pointer.
// Exceptions: when the array is the argument of the `&` (address-of) operator: // Exception: when the array is the argument of the `&` (address-of) operator:
int arr[10]; int arr[10];
int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`! int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`!
// It's of type "pointer to array" (of ten `int`s). // It's of type "pointer to array" (of ten `int`s).
@ -514,22 +524,23 @@ int main (int argc, char** argv)
// You can also dynamically allocate contiguous blocks of memory with the // You can also dynamically allocate contiguous blocks of memory with the
// standard library function malloc, which takes one argument of type size_t // standard library function malloc, which takes one argument of type size_t
// representing the number of bytes to allocate (usually from the heap, although this // representing the number of bytes to allocate (usually from the heap,
// may not be true on e.g. embedded systems - the C standard says nothing about it). // although this may not be true on e.g. embedded systems - the C standard
// says nothing about it).
int *my_ptr = malloc(sizeof(*my_ptr) * 20); int *my_ptr = malloc(sizeof(*my_ptr) * 20);
for (xx = 0; xx < 20; xx++) { for (xx = 0; xx < 20; xx++) {
*(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx *(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints) } // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints)
// Be careful passing user-provided values to malloc! If you want // Be careful passing user-provided values to malloc! If you want to be safe,
// to be safe, you can use calloc instead (which, unlike malloc, also zeros out the memory) // you can use calloc instead (which, unlike malloc, also zeros the memory)
int* my_other_ptr = calloc(20, sizeof(int)); int* my_other_ptr = calloc(20, sizeof(int));
// Note that there is no standard way to get the length of a // Note that there is no standard way to get the length of a dynamically
// dynamically allocated array in C. Because of this, if your arrays are // allocated array in C. Because of this, if your arrays are going to be
// going to be passed around your program a lot, you need another variable // passed around your program a lot, you need another variable to keep track
// to keep track of the number of elements (size) of an array. See the // of the number of elements (size) of an array. See the functions section
// functions section for more info. // for more info.
size_t size = 10; size_t size = 10;
int *my_arr = calloc(size, sizeof(int)); int *my_arr = calloc(size, sizeof(int));
// Add an element to the array // Add an element to the array
@ -541,9 +552,9 @@ int main (int argc, char** argv)
} }
my_arr[10] = 5; my_arr[10] = 5;
// Dereferencing memory that you haven't allocated gives // Dereferencing memory that you haven't allocated gives "unpredictable
// "unpredictable results" - the program is said to invoke "undefined behavior" // results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash. printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash
// When you're done with a malloc'd block of memory, you need to free it, // When you're done with a malloc'd block of memory, you need to free it,
// or else no one else can use it until your program terminates // or else no one else can use it until your program terminates
@ -552,8 +563,8 @@ int main (int argc, char** argv)
// Strings are arrays of char, but they are usually represented as a // Strings are arrays of char, but they are usually represented as a
// pointer-to-char (which is a pointer to the first element of the array). // pointer-to-char (which is a pointer to the first element of the array).
// It's good practice to use `const char *' when referring to a string literal, // It's good practice to use `const char *' when referring to a string literal
// since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.) // since string literals shall not be modified (i.e. foo[0] = 'a' is ILLEGAL.)
const char *my_str = "This is my very own string literal"; const char *my_str = "This is my very own string literal";
printf("%c\n", *my_str); // => 'T' printf("%c\n", *my_str); // => 'T'
@ -635,11 +646,11 @@ printf("first: %d\nsecond: %d\n", first, second);
// Return multiple values. // Return multiple values.
// C does not allow for returning multiple values with the return statement. If // C does not allow for returning multiple values with the return statement. If
// you would like to return multiple values, then the caller must pass in the // you would like to return multiple values, then the caller must pass in the
// variables where they would like the returned values to go. These variables must // variables where they would like the returned values to go. These variables
// be passed in as pointers such that the function can modify them. // must be passed in as pointers such that the function can modify them.
int return_multiple( int *array_of_3, int *ret1, int *ret2, int *ret3) int return_multiple(int *array_of_3, int *ret1, int *ret2, int *ret3)
{ {
if(array_of_3 == NULL) if (array_of_3 == NULL)
return 0; //return error code (false) return 0; //return error code (false)
//de-reference the pointer so we modify its value //de-reference the pointer so we modify its value
@ -672,15 +683,15 @@ printIntArray(my_arr, size);
// will print "arr[0] is: 1" etc // will print "arr[0] is: 1" etc
*/ */
// if referring to external variables outside function, you should use the extern keyword. // if referring to external variables outside function, use the extern keyword.
int i = 0; int i = 0;
void testFunc() { void testFunc(void) {
extern int i; //i here is now using external variable i extern int i; //i here is now using external variable i
} }
// make external variables private to source file with static: // make external variables private to source file with static:
static int j = 0; //other files using testFunc2() cannot access variable j static int j = 0; //other files using testFunc2() cannot access variable j
void testFunc2() { void testFunc2(void) {
extern int j; extern int j;
} }
// The static keyword makes a variable inaccessible to code outside the // The static keyword makes a variable inaccessible to code outside the
@ -693,6 +704,18 @@ void testFunc2() {
// declared with some other starting value. // declared with some other starting value.
//**You may also declare functions as static to make them private** //**You may also declare functions as static to make them private**
// Note that before C23, and unlike C++, functions taking no arguments without
// an explicit `void` inside the parameter list will be treated as taking an
// unknown number of arguments rather than no arguments.
void testFunc3(void) {
// Functions can be prototyped inside other functions
void foobie();
void bletch(void);
foobie(1, 2, 3); // This will give a warning at most, not an error
bletch(1, 2, 3); // This will produce an error
}
/////////////////////////////////////// ///////////////////////////////////////
// User-defined types and structs // User-defined types and structs
/////////////////////////////////////// ///////////////////////////////////////
@ -713,10 +736,16 @@ struct rectangle {
// due to potential padding between the structure members (this is for alignment // due to potential padding between the structure members (this is for alignment
// reasons). [1] // reasons). [1]
void function_1() void function_1(void)
{ {
struct rectangle my_rec = { 1, 2 }; // Fields can be initialized immediately struct rectangle my_rec = { 1, 2 }; // Fields can be initialized immediately
// Fields can also be initialized in an arbitrary order with the field name
struct rectangle my_rec2 = {
.height = 2,
.width = 1
};
// Access struct members with . // Access struct members with .
my_rec.width = 10; my_rec.width = 10;
my_rec.height = 20; my_rec.height = 20;
@ -760,10 +789,10 @@ int areaptr(const rect *r)
// Function pointers // Function pointers
/////////////////////////////////////// ///////////////////////////////////////
/* /*
At run time, functions are located at known memory addresses. Function pointers are At run time, functions are located at known memory addresses. Function pointers
much like any other pointer (they just store a memory address), but can be used are much like any other pointer (they just store a memory address), but can be
to invoke functions directly, and to pass handlers (or callback functions) around. used to invoke functions directly, and to pass handlers (or callback functions)
However, definition syntax may be initially confusing. around. However, definition syntax may be initially confusing.
Example: use str_reverse from a pointer Example: use str_reverse from a pointer
*/ */
@ -773,12 +802,13 @@ void str_reverse_through_pointer(char *str_in) {
f = &str_reverse; // Assign the address for the actual function (determined at run time) f = &str_reverse; // Assign the address for the actual function (determined at run time)
// f = str_reverse; would work as well - functions decay into pointers, similar to arrays // f = str_reverse; would work as well - functions decay into pointers, similar to arrays
(*f)(str_in); // Just calling the function through the pointer (*f)(str_in); // Just calling the function through the pointer
// f(str_in); // That's an alternative but equally valid syntax for calling it. // f(str_in); // That's an alternative but equally valid syntax for calling it
} }
/* /*
As long as function signatures match, you can assign any function to the same pointer. As long as function signatures match, you can assign any function to the same
Function pointers are usually typedef'd for simplicity and readability, as follows: pointer. Function pointers are usually typedef'd for simplicity and readability,
as follows:
*/ */
typedef void (*my_fnp_type)(char *); typedef void (*my_fnp_type)(char *);
@ -864,8 +894,8 @@ as the C file.
*/ */
/* A safe guard to prevent the header from being defined too many times. This */ /* A safe guard to prevent the header from being defined too many times. This */
/* happens in the case of circle dependency, the contents of the header is */ /* happens in the case of circlular dependencies, or such as when a header is */
/* already defined. */ /* included alongside a header that includes the same header itself. */
#ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */ #ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */
#define EXAMPLE_H /* Define the macro EXAMPLE_H. */ #define EXAMPLE_H /* Define the macro EXAMPLE_H. */
@ -895,9 +925,10 @@ typedef struct Node
/* So can enumerations. */ /* So can enumerations. */
enum traffic_light_state {GREEN, YELLOW, RED}; enum traffic_light_state {GREEN, YELLOW, RED};
/* Function prototypes can also be defined here for use in multiple files, */ /* Function prototypes can also be defined here for use in multiple files, */
/* but it is bad practice to define the function in the header. Definitions */ /* but it is bad practice to define the function in the header. Definitions */
/* should instead be put in a C file. */ /* should instead be put in a C file. An uncommon exception is when defining */
/* static inline functions. */
Node createLinkedList(int *vals, int len); Node createLinkedList(int *vals, int len);
/* Beyond the above elements, other definitions should be left to a C source */ /* Beyond the above elements, other definitions should be left to a C source */