This commit is contained in:
Chris Harding 2024-10-05 23:31:06 +05:30 committed by GitHub
commit a0bd453d17
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"]
- ["Dragos B. Chirila", "https://github.com/dchirila"]
- ["Heitor P. de Bittencourt", "https://github.com/heitorPB/"]
- ["Chris Harding", "https://github.com/sjrct"]
---
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`
>
> 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
// 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.
// For your own headers, use double quotes instead of angle brackets, and
// provide the path:
#include "my_header.h" // local file
#include "my_header.h" // local file
#include "../my_lib/my_lib_header.h" //relative path
// Declare function signatures in advance in a .h file, or at the top of
// your .c file.
void function_1();
int function_2(void);
void function_1(void);
int function_2(int a, float b);
// At a minimum, you must declare a 'function prototype' before its use in any function.
// Normally, prototypes are placed at the top of a file before any function definition.
// At a minimum, you must declare a 'function prototype' before its use in any
// 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
// although `int add_two_ints(int, int);` is also valid (no need to name the args),
// it is recommended to name arguments in the prototype as well for easier inspection
// although `int add_two_ints(int, int);` is also valid (no need to name the
// 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
// any other function that calls that function. However, it's standard practice to
// always add the function prototype to a header file (*.h) and then #include that
// file at the top. This prevents any issues where a function might be called
// before the compiler knows of its existence, while also giving the developer a
// clean header file to share with the rest of the project.
// any other function that calls that function. However, it's standard practice
// to always add the function prototype to a header file (*.h) and then #include
// that file at the top. This prevents any issues where a function might be
// called before the compiler knows of its existence, while also giving the
// 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
// 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;
// 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 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.).
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
// is not evaluated (except VLAs (see below)).
// If the argument of the `sizeof` operator is an expression, then its
// argument is not evaluated (except VLAs (see below)).
// The value it yields in this case is a compile-time constant.
int a = 1;
// 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:
int my_array[20] = {0};
// 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};
// So my_array now has five elements, all but the first two of which are 0:
// [1, 2, 0, 0, 0]
// NOTE that you get away without explicitly declaring the size
// of the array IF you initialize the array on the same line:
// NOTE that you get away without explicitly declaring the size of the
// array IF you initialize the array on the same line:
int my_array[] = {0};
// NOTE that, when not declaring the size, the size of the array is the number
// of elements in the initializer. With "{0}", my_array is now of size one: [0]
// NOTE that, when not declaring the size, the array's size is the number of
// 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
// byte size of its element type:
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
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
i1 / (double)i2; // => 0.5 // Same with double
f1 / f2; // => 0.5, plus or minus epsilon
@ -366,13 +371,17 @@ int main (int argc, char** argv)
printf("\n");
// *****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;
for (i = 0; i <= 5; i++) {
; // use semicolon to act as the body (null statement)
}
// 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()
switch (a) {
@ -441,8 +450,9 @@ int main (int argc, char** argv)
// without warning.
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`,
// respectively, use the CHAR_MAX, SCHAR_MAX and UCHAR_MAX macros from <limits.h>
// For determining the max value of a `char`, a `signed char` and an
// `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.
printf("%f\n", (double) 100); // %f always formats a double...
@ -453,9 +463,9 @@ int main (int argc, char** argv)
// Pointers
///////////////////////////////////////
// A pointer is a variable declared to store a memory address. Its declaration will
// also tell you the type of data it points to. You can retrieve the memory address
// of your variables, then mess with them.
// A pointer is a variable declared to store a memory address. Its declaration
// will also tell you the type of data it points to. You can retrieve the
// memory address of your variables, then mess with them.
int x = 0;
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;
// 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.
// For example, when an array is passed to a function or is assigned to a pointer,
// it decays into (implicitly converted to) a pointer.
// Exceptions: when the array is the argument of the `&` (address-of) operator:
// For example, when an array is passed to a function or is assigned to a
// pointer, it decays into (implicitly converted to) a pointer.
// Exception: when the array is the argument of the `&` (address-of) operator:
int arr[10];
int (*ptr_to_arr)[10] = &arr; // &arr is NOT of type `int *`!
// 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
// 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
// may not be true on e.g. embedded systems - the C standard says nothing about it).
// representing the number of bytes to allocate (usually from the heap,
// 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);
for (xx = 0; xx < 20; xx++) {
*(my_ptr + xx) = 20 - xx; // my_ptr[xx] = 20-xx
} // Initialize memory to 20, 19, 18, 17... 2, 1 (as ints)
// Be careful passing user-provided values to malloc! If you want
// to be safe, you can use calloc instead (which, unlike malloc, also zeros out the memory)
// Be careful passing user-provided values to malloc! If you want to be safe,
// you can use calloc instead (which, unlike malloc, also zeros the memory)
int* my_other_ptr = calloc(20, sizeof(int));
// Note that there is no standard way to get the length of a
// dynamically allocated array in C. Because of this, if your arrays are
// going to be passed around your program a lot, you need another variable
// to keep track of the number of elements (size) of an array. See the
// functions section for more info.
// Note that there is no standard way to get the length of a dynamically
// allocated array in C. Because of this, if your arrays are going to be
// passed around your program a lot, you need another variable to keep track
// of the number of elements (size) of an array. See the functions section
// for more info.
size_t size = 10;
int *my_arr = calloc(size, sizeof(int));
// Add an element to the array
@ -541,9 +552,9 @@ int main (int argc, char** argv)
}
my_arr[10] = 5;
// Dereferencing memory that you haven't allocated gives
// "unpredictable results" - the program is said to invoke "undefined behavior"
printf("%d\n", *(my_ptr + 21)); // => Prints who-knows-what? It may even crash.
// Dereferencing memory that you haven't allocated gives "unpredictable
// results" - the program is said to invoke "undefined behavior"
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,
// 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
// 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,
// since string literals shall not be modified (i.e. "foo"[0] = 'a' is ILLEGAL.)
// 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.)
const char *my_str = "This is my very own string literal";
printf("%c\n", *my_str); // => 'T'
@ -635,11 +646,11 @@ printf("first: %d\nsecond: %d\n", first, second);
// Return multiple values.
// 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
// variables where they would like the returned values to go. These variables 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)
// variables where they would like the returned values to go. These variables
// 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)
{
if(array_of_3 == NULL)
if (array_of_3 == NULL)
return 0; //return error code (false)
//de-reference the pointer so we modify its value
@ -672,15 +683,15 @@ printIntArray(my_arr, size);
// 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;
void testFunc() {
void testFunc(void) {
extern int i; //i here is now using external variable i
}
// make external variables private to source file with static:
static int j = 0; //other files using testFunc2() cannot access variable j
void testFunc2() {
void testFunc2(void) {
extern int j;
}
// The static keyword makes a variable inaccessible to code outside the
@ -693,6 +704,18 @@ void testFunc2() {
// declared with some other starting value.
//**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
///////////////////////////////////////
@ -713,10 +736,16 @@ struct rectangle {
// due to potential padding between the structure members (this is for alignment
// reasons). [1]
void function_1()
void function_1(void)
{
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 .
my_rec.width = 10;
my_rec.height = 20;
@ -760,10 +789,10 @@ int areaptr(const rect *r)
// Function pointers
///////////////////////////////////////
/*
At run time, functions are located at known memory addresses. Function pointers are
much like any other pointer (they just store a memory address), but can be used
to invoke functions directly, and to pass handlers (or callback functions) around.
However, definition syntax may be initially confusing.
At run time, functions are located at known memory addresses. Function pointers
are much like any other pointer (they just store a memory address), but can be
used to invoke functions directly, and to pass handlers (or callback functions)
around. However, definition syntax may be initially confusing.
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; would work as well - functions decay into pointers, similar to arrays
(*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.
Function pointers are usually typedef'd for simplicity and readability, as follows:
As long as function signatures match, you can assign any function to the same
pointer. Function pointers are usually typedef'd for simplicity and readability,
as follows:
*/
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 */
/* happens in the case of circle dependency, the contents of the header is */
/* already defined. */
/* happens in the case of circlular dependencies, or such as when a header is */
/* included alongside a header that includes the same header itself. */
#ifndef EXAMPLE_H /* if EXAMPLE_H is not yet defined. */
#define EXAMPLE_H /* Define the macro EXAMPLE_H. */
@ -895,9 +925,10 @@ typedef struct Node
/* So can enumerations. */
enum traffic_light_state {GREEN, YELLOW, RED};
/* 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 */
/* should instead be put in a C file. */
/* 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 */
/* should instead be put in a C file. An uncommon exception is when defining */
/* static inline functions. */
Node createLinkedList(int *vals, int len);
/* Beyond the above elements, other definitions should be left to a C source */