Skip to content

Instantly share code, notes, and snippets.

@stifskere
Last active July 28, 2024 23:36
Show Gist options
  • Save stifskere/819812688a9793ade1c0ba1ed2269551 to your computer and use it in GitHub Desktop.
Save stifskere/819812688a9793ade1c0ba1ed2269551 to your computer and use it in GitHub Desktop.
A c english tutorial
/*
C Tutorial !!11!1!!1
This is a simple C tutorial to understand the basics of the language,
not including most of the linkable libraries or advanced compiler behavior.
*/
// Let's start with includes, includes let you concatenate
// a file to another file in your program, `.h` or header
// files are directly included on import, but there are
// `.c` files that should contain the definition of these header
// files, to include a header file you use the #include
// pre-processor directive and <> after if the header file
// is external or "" if the header file is local.
//
// TDLR; these files allow you using code writen in other files.
#include <stdio.h>
#include <malloc.h>
#include <math.h>
// we include these both headers as they will be important for this tutorial
// Let's talk about functions, a function is a piece of code
// you can reuse and call with some specified arguments
// and a return value, the sintax for a function is the
// following
// <return type> <name> (<arguments separated by ,>) {<function definition>}
void some_function(int argc, void *argv) {
}
// We will discuss later what those arguments mean,
// but for now we can say that we are able to call this function
// from inside another function, in this case we can call it
// from the main function.
// This function is unique, as it's the
// entry point of your program, the program will start
// running from here.
//
// This function returns an integer which is the exit code
// for the program
//
// https://en.wikipedia.org/wiki/Exit_status
int main() {
// The program starts here, you can write instructions
// call other functions and shape your program from here.
// Lets start talking about variables and types.
// The syntax for a variable is the following
//
// <type> <name> = <value expression>;
//
// Everything you write will end in a ;, except braces,
// braces don't need to end in a ;.
int variable = 10;
// Now we have a variable we can reference, its type is int (integer)
// and its value is 10, now when we reference this we will get the value 10.
// In this language only actual primitives exist, these are char, short, int, long, float
// and double, some of this types can be modified, for example if we add the "unsigned" prefix
// the number will not have negative numbers, and the memory that would be dedicated on having
// negative numbers is allocated for having more positive numbers, for example
//
// "char" range: -128 to 128
// "unsiged char" range: 0 to 255
//
// The size of char is equivalent to a byte, but we don't have the byte type in c.
// You can check what modifiers you can use and why in this table
//
// https://en.wikipedia.org/wiki/C_data_types
//
// char is more than a number, as it's name might tell you it's also a character.
// whenever you print a char to stdout you will see a letter, letters in computers
// are tied to a table that converts them to numbers and viceversa.
//
// https://es.wikipedia.org/wiki/ASCII
//
// We can find literals for every type mentioned above, a literal is the definition
// of the number, for example we could say that 10 is a literal for int,
// 10L is a literal for 10 as a long integer, the postfixes for defining literal
// as other types are the following (The operators can be merged the same way as merging
// types for variables)
//
// U unsigned
// L long int
// UL unsigned long int
// LL long long int
// ULL unsigned long long int
//
// And then there are prefixes which are other ways of writting numeric literals,
// these don't really modify the type, is mostly only ways to read the code.
//
// 0x hexadecimal,
// 0 octal
// 0b binary
//
// And lastly there is the literal for char which is '' with the character in between,
// this is not like python or other languages that might let you use whatever quotes you want
//
// 'A' means A char or 17
// In C we can apply forms of conversion, by using a cast, a cast is defined using ()
// and the type in between, for example
int variable1 = 17;
char variable2 = (char)variable1; // A
// There is 2 types of conversion we can find, which is implicit and explicit,
// implicit conversion is automatic, meaning we don't have to do anything
// to convert, it's part of the language behavior, while explicit
// means we need to do what's written above to convert a type.
// C has basic operators appliable to numbers, you can use those to interact with
// the numbers you store in variables.
//
// With operators you can do complex expressions like basic maths, the only difference
// this has is the fact you can grab variable values and run functions in the middle
// of the expression to get numbers you can use to for example get a random number
// or get a number from somewhere else.
//
// The operators you can find in C are the following
//
// + Adds 2 operands
// - Subtracts 2 operands
// * Multiplies 2 operands
// / Divides 2 operands
// % Get the modulus of the division of 2 operands
// ++ Increment a variable by 1
// -- Decrement a variable by 1
// = Assign a variable by the right operand
//
// The 3 last operators are to be used only in variable references, these act as
// expressions, and return the following values:
//
// <variable>++ returns the value and then increments the variable
// ++<variable> increments the variable and returns the incremented variable value
//
// The same happens with --, and for the = operator simply returns the right operand
// while asigning the variable reference to this right operand too.
//
// These last operators act in numbers, but then there are also boolean operators,
// even tho booleans in c do not exist, we can translate those as 0 or 1, 0 being
// false and 1 being true, if, while and for statements will expect numbers
// instead of booleans.
//
// == Compares the equality of 2 numbers, returning 1 if both of the operands are equal
// != Compares the inequality of 2 numbers, returning 1 if both of the operands are not equal
// > Compares if the left operand is bigger than the right one, returns 1 if so
// < Compares if the left operand is smaller than ther right one, returns 1 if so
// >= Compares if the left operand is bigger or equal to the right one, returns 1 if so
// <= Compares if the left operand is smaller or equal to the right one, returns 1 if so
//
// You can merge these operators and make complex expressions the following way
//
// && boolean AND operator, returns 1 if both operands are true
// || boolean OR operator, returns 1 if at least 1 operand is true
// ! boolean negation operator, returns the contrary value for example 0 would become 1 and viceversa
//
// For example we could say that "10 == 10 && 5 != 6" returns true because
//
// 10 = 10 && 5 != 6
// 1 && 5 != 6
// 1 && 1
// 1
//
// Then we have bitwise operators, which compare and modify bits in numbers,
// for everything we can do with bitwise operators we need to view numbers as binary
//
// for example 10 = 00001010
//
// Lets play with the 10 and 11 for the examples
//
// 00001011
// 00001010
// 00001010 & (AND OPERATOR)
//
// The result is 10, as it applies the oprerator for every bit and
// checks every bit instead of the literal number.
//
// 00001011
// 00001010
// 00001011 | (OR OPERATOR)
//
// 00001011
// 00001010
// 00000001 ^ (XOR OPERATOR)
//
// Here are the conversion tables for the bitwise operators
//
// 0 & 0 = 0
// 1 & 0 = 0
// 0 & 1 = 0
// 1 & 1 = 1
//
// 0 | 0 = 0
// 1 | 0 = 1
// 0 | 1 = 1
// 1 | 1 = 1
//
// 0 ^ 0 = 0
// 1 ^ 0 = 1
// 0 ^ 1 = 1
// 1 ^ 1 = 0
//
// and then there is a few more operators like the shift and invert operator,
// the invert operator acts the same as !, but for binary
//
// 00001010
// 11110101 ~
//
// ~ 1 = 0
// ~ 0 = 1
//
// And about the shift operator simply moves the bits x positions.
//
// 00000001
// 00000001
// 00000010 <<
//
// 00000010
// 00000001
// 00000001 >>
//
// If the width is smaller than the shifted ammount it results in 0, which means overflow,
// for example for unsigned char which has a size of 8 bits, if we shift 9 we will get 0
// as result
//
// 00000001
// 00001001
// 00000000 >> or <<
//
// The bitwise operators can be used for optimizing the usage of memory if you have payloads
// and OP codes and some other cases, or to store properties in enum constants.
//
// All the binary (not bitwise but bitwise too) operators have their X= version, which uses
// a variable as the left operand and directly assigns the result to that variable.
//
// The types of operators are binary (2 operands are required) and unary (only one
// operand is required), for example, we can increment a variable by 10 by doing
variable += 10; // since variable value was 10, now the value will be 20,
// this also returns 20 as expression.
// C operators run in pedmas/bodmas order, we can use () to override that.
//
// 2 + 5 * 3 == 17
// (2 + 5) * 3 == 21
// C also has arrays which is considered a complex structure, these are fixed size
// lists of values of a single type, and are declared the following way
int variable4[] = {1, 2, 3, 4};
// Even tho before in this tutorial we specified that after {} a ; is not required
// this is not a control statement nor a body, so it's required in this case.
//
// the syntax for an array is the following
//
// <type> <name>[] = {<, separated values>};
//
// Any type can be stored in an array, even sub arrays, or other complex structures you might declare,
// there is also another way of declaring arrays which is by size, we can do the following
int variable5[10];
// What that does in this case is start the variable with 10 positions and a 0 in each position,
// as 0 is the default value for any non complex data type.
//
// To access array values or assign them we can do it the following way
//
// variable4[0]
//
// And the last expression will return 1, as the 0th position of the variable4 array is 1,
// position starts with 0, there is no length concept in C, we need to keep track of that
// or get it by measuring with the sizeof() operator, which returns the size of a data type
// or literal data itself
int var4Size = sizeof(variable4) / sizeof(int);
// And that would return 4, because variable4 returns the size in bytes of a structure
// or the bytes a type takes when allocated, since int is 4 bytes, sizeof(variable4)
// would return 16, because the array takes 16 bytes in memory, and if we divide that
// by the size of the type which is 4, we get 4, as 16 / 4 is 4.
//
// We can also asign a value to each place, for example to asign a value to
// variable5[0] we could do the following
variable5[0] = 10;
// Note that we can also nest arrays, and when we access a value of the array
// it returns whatever it stores and lets us manage it how the type is specified
// to do so, if we have an array of arrays and access the index 0 of that,
// we get the reference to another array, which you could access to a custom
// index of that array so <ref>[0][0] == 1 if {{1, 2}, {3, 4}}, the type for
// a nested array is <type>[][] and we can nest it as many times as we need.
// Arrays are directly tied to pointers, now that we understand how arrays work,
// we must understand one of the most important ways of data manipulation in C
// which are pointers, pointers lets us move trough memory as if it was an array,
// we can take the address of a variable and store it in a pointer variable,
// if we modify that pointer variable since it's pointing to the same memory
// address as the variable, both variables will be altered, we can also move
// positions within pointers.
// Lets take RAM as if it was a big array of bytes, if we point to some variable
// we might get a big memory address such as 0x7ffee3b9e0c0, lets for example
// take a pointer to the first item of the variable4 array.
int *var4Ptr = variable4;
// This stores the address of the first item of the array, we don't need
// to take the reference as arrays are just another way of manipulating
// memory like pointers, and both are implicitly converted to one another.
// If we wanted to get the reference to a variable, we would need to tell C
// we want the reference of that variable with the & operator.
int *varPtr = &variable;
// now this would actually give us the address of the element in that variable,
// the point of having pointers is being able to modify the same value in different places
// referencing the same memory, to modify the value of variable trough varPtr, we can do
// the following
(*varPtr) = 50;
// When we use * in a pointer we get the reference to that position, we can then
// assign a value to that reference and the value will be changed for both
// variables, as both point to the same place.
//
// If we declare a pointer to an array we can safely move trough it incrementing the
// memory address as if it was a number, it will then point to other parts in the memory,
// we specify types in pointers for that reason, so when we move it moves sizeof(<type>) times
// instead of only 1 byte.
var4ptr++; // now assuming that var4Ptr value was 0x7ffee3b9e0c0 it is incremented by 4
// because it's an int pointer and int is 4 bytes, now we have access to
// the next value address.
// To simply retrieve a value we can use the (*ptr) operator and it will
// return a reference to that memory address, which depending on the
// type it will be some size or the other, but the point here is that we
// get a manipulable value.
// If we want to grab different types of pointers and increase or decrease the
// memory address manually we can use a void* type.
void *genericPtr = var4Ptr; // This assigns this ptr to the same address as var4Ptr;
// but in a generic way since this is an int array, we will
// need to manually increase or decrease by 4 to move
// around the array.
genericPtr += 4; // advances to the next position and assigning an int to it will replace
// the next 4 bytes, (an explicit cast is recomended for safety)
(*genericPtr) = (int)5;
// if we had arrays of arrays that would be equivalent to (<type> *<name>)[<size of each array>]
// and incrementing would increment by sizeof(<type>) * <array size> * times
// The most used case of that is string arrays, since in here we don't have strings we use
// char*, and char* has a literal that's "", and all the text inside, that will return
// us an array of size sizeof(char) * sizeof(value), but since sizeof(char) returns 1, we can
// simplify to sizeof(value).
char *text = "Hello World!"; // size = 12
// and interact with that like a pointer, we could also have it as a int <name>[], as again, both types
// can be implcitly casted.
//
// Like arrays, we can also nest pointers, to do so, we simply declare a int** and that
// would be the same as int <name>[][], accessing a value of the index assuming we used
// the pointer in a safe maneer gives us a reference to another pointer, we can manage
// like the first one (*<ref>)++ would increment the internal pointer, and accessing
// the value would be like (**<ref>).
// In C we have some useful apis that can be imported, such as malloc, stdio and cmath.
// Let's talk abut malloc first, malloc is a header file that allows us to allocate and free
// memory in a custom way, we can allocate as many bits as we want and then fill those programatically
// malloc declares 2 main functions along some others, these are malloc and free, lets talk about malloc first
//
// malloc(int size) allows us to allocate as many memory as we want if it's free in the system, it returns a
// void* that we can cast to any type we want.
//
// for example a jagged array, which is an array that it's sub arrays have different sizes each.
//
// https://en.wikipedia.org/wiki/Jagged_array
//
// A basic use of malloc is
int *allocated = (int *)malloc(5 * sizeof(int)); // 4 * 5 == 20
// Now we can manipulate that pointer as if it was a normal pointer, but we should keep the first address
// to free it after, because this isn't cleared by C after finishing the scope, as it's not statically
// allocated in any way.
//
// The safe use for a malloced pointer is to first check if the malloc function didn't return 0,
// if it did we can say the allocation failed for many reasons, such as corrupted memory or
// insufficient memory, 0 is a reserved memory address which we can consideer null or None in
// python.
//
// To free it we use free, that takes the first address and clears the allocated size.
//
// If we need to create a copy of the first address to clear them so we don't need to keep
// track of how many we moved, we can do the following
int *fAllocated = allocated;
// That before doing anything with the allocated memory.
//
// To free it we use the free function that returns void.
free(fAllocated);
// There is also another library that we can use for printing values, writing to files
// and more, you can check the stdio.h reference in wikipedia
//
// https://es.wikipedia.org/wiki/Stdio.h
//
// Printing to the terminal can be done with printf which means print format,
// lets us format whatever we want to print and it takes 2 arguments
//
// printf(char *, ...); the last argument means varargs (as many arguments as we want of any type)
//
// https://en.wikipedia.org/wiki/Variadic_function
//
// There is a format specification for including the arguments inside the string, the basic ones
// being %s which is include next string and %d which is include next integer or %c which
// is include next char.
//
// as an example
int apples = 50;
printf("there are %d apples", apples);
// if we pass more arguments than we use or less we might get undefined behavior.
//
// This library also includes scanf, which lets us take input from the user,
// we should take care with that because we can get literally anything from
// the user, so we should always be sure that the data we receive
// is clean by using control statements.
char *input;
scanf("%s", *input);
// The value of input should now be whatever the user introduced, we can get
// the length with sizeof.
// lastly, there is cmath, which contains a lot of mathematical functions
// that are mostly shortcuts to complex expressions we might write such as pow
// or round...
//
// https://cplusplus.com/reference/cmath/
//
// The python math library is just bindings to cmath.
//
// We will not be doing any example of cmath, because the reference has many.
//
// Before going into control statements we will talk about error handling, in
// c there are no errors, there is nothing that tells you "you fucked up in that line",
// you most likely get a segmentation fault for pointers or any other error
// that might be tied to another exit code, note that when you get a segmentation fault
// the used memory doesn't free so you will keep it till you restart the computer.
//
// https://www.gnu.org/software/libc/manual/html_node/Error-Codes.html
//
// In C there are a few types of errors which is compiler error, linker error
// and runtime error.
//
// A compiler error will give us the description of what went wrong, for example
// a syntax error such as an unclosed parenthesis.
//
// A linker error is an error that will occur when the linker had a problem
// while finding the necessary files to feed the compiler with.
//
// and the runtime error that doesn't really give any information
// are errors that happen while the program is running due to a logic error
// or something we did in our program that doesn't work.
//
// For example dividing by 0 would be considered a logic error.
//
// Now lets get started with control statements, lets talk about the if statement
// the if statement gives us the possibility to run code conditionally, for example
// if we have the age of a person and want to check whether they are older than 18 to
// let them buy some controlled substance or to be issued a driving license (in europe)
// we can do the following
int personAge = 15;
if (personAge >= 18) {
printf("You are permited to drive");
} // control statements don't end with ;
// if statments have else clauses which run whenever the first condition is not met.
if (personAge >= 18 && personAge <= 70) {
printf("You may drive.");
} else {
printf("Get better.");
}
// or we could divide that to give different results with else if
if (personAge < 18) {
printf("You might not drive, to little.");
} else if (personAge > 70) {
printf("Too old, not acceptable.");
} else {
printf("here is your license.");
}
// And that's really it with the if statements, we could remove the {}
// if we are only executing one expression, if we remove the {}
// no assignments are allowed within the if scope,
// if we declare a variable inside the if scope we can't access to it
// outside of the scope, but otherwise if we declare a variable outside
// we can access inside.
// Then there are for loops, which are loop control statements, they take
// a variable declaration (since C11), a condition to check whether
// it should keep looping and a control statement to modify a condition
// variable, for example
// i is 0, while i < 10 then we run the inner code and increment the variable, recheck till false.
for (int i = 0; i < 10; i++)
printf("%d", i); // we can use {} or not, however we want.
// This prints 0 trough 9.
// Any kind of condition can be passed to loops, this is just an example, we can chose to
// not place one or all of the parts of the loop, the loop would work with only
for (;;) {} // wich would be an infinite loop.
// While loops work a different way, we can run the first iteration
// before or after the loop
int iterator = 0;
while (iterator < 10)
printf("%d", iterator++);
// The last code would print 0 trough 9.
// or do while loops
iterator = 0;
do {
printf("%d", iterator);
} while (iterator++ < 10); // a ; is required.
// This code would also print 0 trough 9.
// This could be used to iterate arrays or whatever you want to do, use your imagination.
// We can use the break or continue keyword in all of the loop statements
//
// The contnue keyword can be used to advance to the next iteration ignoring code below,
// the break keyword can be used to exit the loop and continue with the code below.
//
// And there is also the switch statement, which is like a big if statement but
// that only accepts checking numbers, not strings or any other complex structure.
char grade;
printf("Enter your letter grade (A, B, C, D, or F): ");
scanf("%c", &grade); // get a grade.
switch (grade) { // test case
case 'A': // grade == A
printf("Excellent!\n"); // print excellent
break; // don't check other cases, if absent the switch will keep going.
case 'B':
printf("Very good!\n");
break;
case 'C':
printf("Good job!\n");
break;
case 'D':
printf("You passed, but you can do better.\n");
break;
case 'F':
printf("Uh oh, you failed. Time to study!\n");
break;
default: // if grade is none of the above.
printf("Invalid grade entered.\n");
}
// There is another expression we might use to assign a variable conditionally.
// That's called ternary operator, and it's syntax is
//
// (<condition> ? <if true> : <otherwise>)
//
// Here is an example assigning it to a variable
char *result = personAge >= 18 ? "might pass" : "will definitively not pass";
// assuming person age is 15 the result variable will contain "will definitively not pass"
} // This is all we can do within a function, but in reality all we need are numbers, other abstractions
// are unnecesary, leave that to javascript developers.
// In here we can declare other functions, structs and enums, we can also talk about preprocessor statements
// which are scope agonostic.
// structs are a complex data structure we can use to organize data, it's a collection of memory addresses, and it's size
// is a sum of all the data inside the struct.
struct Person {
char *name;
int age;
};
// Whatever you declare inside a struct will be allocated as normal variables, but since you have a collection
// of memory addresses you can access to the values in the same place from a C point of view.
// to declare a variable of this type we can do it the following way, this is a global variable that can be
// used after declaration.
struct Person person = {"john", 23}; // to construct a Person we pass the data in order of field declaration
// Alternatively if we don't want to write the struct keyword in every instance we can use typedef, which
// is a keyword that can only be used in top level to declare type expressions,
//
// The syntax is the following
//
// typedef <expression> <name>;
//
// for example we could declare a struct and a room.
typedef struct {
char *name;
int age;
} Person;
// and to instantiate the struct we might now use
Person person = {"maria", 46}; // values go by order of declaration.
// To declare a classroom we could do
typedef Person[] ClassRoom;
// and now declaring a variable of type ClassRoom would be like declaring
// an array of Person structs.
ClassRoom person = {{"juan", 20}, {"helena", 18}}; // we initialize the structs inside
// of the array as if it was another
// value.
// Structs also support named declarations which is specifying the name before
// the value, so we can declare it in the order we want.
Person person = {
.name = "pepe", // We assign the name that we declared in the struct earlier.
.age = 15 // Same with the age.
};
// There is another way of storing data which is more linear, it is called union,
// basically unions store the data as subsequent pointers
typedef union {
char *name;
int age;
} UnionPerson;
// to instantiate it
UnionPerson unionPerson = {"john", 30};
// how the data would look in this case, instead of having "random" allocated pointers,
// the data would look like this.
// { j o h n } { int left pad | 30 }
// 01001010 01101111 01101000 01101110 00000000 00000000 00000000 00011110
// Basically we could manage this type safely with a void pointer advancing and
// retrieving the memory we want manually, for the first char* we would advance
// sizeof("john") which is 4 bytes and for the and the int which is 4 bytes more.
// There are also enums which are simply numerical constants, we might
// also use as flags.
//
// Flags can be used to store properties such as permissions, for example
// let's declare a permissions enum
typedef enum { // use typedef because we would need to use enum every instantiation.
Administrator = 1; // 00000001
Read = 1 << 2; // 00000010
Write = 1 << 3; // 00000100
Send = 1 << 4; // 00001000
} Permissions;
// Now we can merge these properties using bitwise operators for example
Permissions perms = Read | Write; // 00000110 // in here we check the bits that are 1 not the actual number
int hasAdmin = (Administrator & perms) != 0;
// 00000001 administrator
// 00000110 perms
// 00000000 & is 0, doesn't have the flag.
// In definition, enums are just numerical constants, a way to give names to numbers so they signify something.
// We can talk more deeply about function definitions now, a function is something you can call, as we saw
// with printf or scanf, those are functions too, to declare your own function you simply do
// <return type> <name>(<, separated arguments>) <body>
int sum(int left, int right) { // the function asks for 2 parameters, left and right.
return left + right; // and returns as result the sum of left and right,
// anything after a return statement is not run.
}
// to call it we can use
int result = sum(10, 20); // and that would result in 30, becasue
// what's stored in the variable is the expression
// after the return statement.
// normally pointers are passed to functions in order for functions to modify the pointer value,
// that can be useful for multiple values or when the function shouldn't return a value or it's not
// semantically well seen that it does.
// We can also chose to separate the function definition from the declaration, this is often done
// with header and c files, header files contain the declaration and c files contain the defintion
// this way what the user includes is more clean and structured.
//
// basicmath.h
int sum(int, int);
int multiply(int, int);
int subtract(int, int);
float divide(int, int);
// basicmath.c
#include <basicmath.h>
int sum(int left, int right) {
return left + right;
}
int multiply(int left, int right) {
return left * right;
}
int subtract(int left, int right) {
return left - right;
}
float divide(int left, int right) {
return left / right;
}
// Now we would be able to use that functions by linking basicmath.c and including basicmath.h in our source file,
// another use case is if we want to define the functions after our main method and want to use them within our main
// method, when we have a declaration we can use the function below that declaration, it will automatically
// try to find the definition and call it.
// Then there are pre processor statements, these allow us to modify our file conditionally based on system
// constants or include other files before compiling, these statement's can't interact
// with runtime code.
//
// Lets start with a #define
//
// A define statement lets us give a name to a piece of code we might repeat, basically a macro.
#define NULL (void *)0
// In here we can conditionally use ;, because the result file will contain the rightmost value.
// in this case (void *)0
//
// if we make an if statement
//
// if (ptr != NULL)
//
// that would be translated to
//
// if (ptr != (void *)0)
//
// and like that we can also undefine variables
#undef NULL
// NULL is already declared in stdlib.h, but that was an example.
//
// About including files, the included file will literally
// be copied and pasted wherever you use the statement,
// if the file contains a variable you can use #include
// to even include it inside a function the same
// way that was mentioned earlier.
//
// Normally including files is made above because
// these normally these declare functions
// and other top level statements.
//
// But in reality you are responsible for your
// design, so you do you.
//
// then there is #if, #else and #elif that let us
// tell the compiler to include a part of the code depending on the
// platform, for example with curses, in windows, linux... is ncurses
// in older macos is curses
//
// #ifdef to check variable availability
// #ifndef to check variable unavailability
#ifdef __APPLE__
#include <AvailabilityMacros.h> // https://opensource.apple.com/source/xnu/xnu-2050.7.9/EXTERNAL_HEADERS/AvailabilityMacros.h.auto.html
#ifdef MAC_OS_X_VERSION_MIN_REQUIRED
#if MAC_OS_X_VERSION_MIN_REQUIRED < 1060
#include <curses.h>
#else
#include <ncurses.h>
#endif
#else
#include <curses.h>
#endif
#else
#include <ncurses.h>
#endif
// These are most variables that are predefined in the system.
// you can also define code in there, which will be included
// or not depending on whether the condition is true or false.
//
// there is another preprocesor statement you can use that's #error
// you could throw a compiler error based on a condition.
#ifdef __APPLE__
#error "header not available in apple"
#endif
// And lastly there is the #pragma pre processor statement, this one lets us
// isue special commands to the compiler using a standarized method.
//
// https://gcc.gnu.org/onlinedocs/cpp/Pragmas.html
//
//
//
//
// Anyways, here is the tutorial, good luck!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment