My C Programming Notes

The "Implementation Defined" Programming Language

Headers and Libraries
✅: Header or function that I think is especially important to know.
❌: Dangerous and unsafe function or outdated header with better alternatives.
🟧: Maybe error prone or with annoying edge cases, but is totally safe when used correctly.
🟣: Other important note.

The C Standard Library (libc) is the main standard library for C. Every so often the standard is updated:
  1. K&R (pre-standardization)
  2. C89/ANSI (first standard)
  3. C90/ISO (ISO became the group responsible for updating the standard)
  4. C95
  5. C99
  6. C11
  7. C17/C18 (Nothing additive. Just revisions of C11)
  8. Future Releases (C23/24) etc.

For more information on this look under "About C" below.

Sometimes there is a group of related functions, macros, or types that only differ on the types they deal with but the functionality is the same. I dont want to clutter the headers with repeated information so they will be colored red to signify that if you hover over them, multiple function prototypes, variable or macro names indicating all the similar types will appear. Any * that appear in the name designate where the differences in the name occur (just like regex). In the example below if you hover over printf the list of related function prototypes will appear. On the otherhand, putc is unique enough of a function that it is by itself if you hover over it.

✅ C Standard Library (Accurate as to C11 Standard*)

C89/ANSI headers:
assert.h
Function-like Macros:
✅ assert()
static_assert()

ctype.h
Functions
isalnum()
isalpha()
isblank()
iscntrl()
isdigit()
isgraph()
islower()
isprint()
ispunct()
isspace()
isupper()
isxdigit()
tolower()
toupper()

errno.h
Macros:
EDOM
EILSEQ
ERANGE

Special Macro: (can return an l or r value)
✅ errno

float.h
Macros:
*_MIN*
*_MAX*
*_DIG
*_EPSILON
*_HAS_SUBNORM
FLT_EVAL_METHOD
FLT_RADIX
FLT_ROUNDS

limits.h
Macros:
CHAR_BIT
*_MIN
*_MAX

locale.h
Types:
struct lconv
Macros:
LC_ALL
LC_COLLATE
LC_CTYPE
LC_MONETARY
LC_NUMERIC
LC_TIME

Functions:
setlocale()
localeconv()

math.h
Pragmas:
STDC FP_CONTRACT

Types:
float_t
double_t

Macros:
HUGE_VAL
HUGE_VALF
HUGE_VALL
INFINITY
NAN
FP_INFINITE
FP_NAN
FP_NORMAL
FP_SUBNORMAL
FP_ZERO
FP_FAST_FMA
FP_FAST_FMAF
FP_FAST_FMAL
FP_ILOGB0
FP_ILOGBNAN
MATH_ERRNO
MATH_ERREXCEPT
math_errhandling

Function-like macros:
🟣Definition to know: "real-floating" refers to a type that is either a: float, double, or long double.
fpclassify()
isfinite()
isinf()
isnan()
isnormal()
signbit()
isgreater()
isgreaterequal()
isless()
islessequal()
islessgreater()
isunordered()

Functions:
🟣All of the functions have a base name and two alternatives where it is suffixed with an 'f' or an 'l'. By default the function is working with doubles, but if you use the 'f' variant then it will work with float types and if you use the 'l' variant it will use long doubles for the return and paramater types. Other paramaters stay the same type.
acos[fl]()
asin[fl]()
atan[fl]()
atan2[fl]()
cos[fl]()
sin[fl]()
tan[fl]()
acosh[fl]()
asinh[fl]()
atanh[fl]()
cosh[fl]()
sinh[fl]()
tanh[fl]()
exp[fl]()
exp2[fl]()
expm1[fl]()
frexp[fl]()
ilogb[fl]()
ldexp[fl]()
log[fl]()
log10[fl]()
log1p[fl]()
log2[fl]()
logb[fl]()
modf[fl]()
scalbn[fl]()
scalbln[fl]()
cbrt[fl]()
fabs[fl]()
hypot[fl]()
pow[fl]()
sqrt[fl]()
erf[fl]()
erfc[fl]()
lgamma[fl]()
tgamma[fl]()
ceil[fl]()
floor[fl]()
nearbyint[fl]()
rint[fl]()
lrint[fl]()
llrint[fl]()
round[fl]()
lround[fl]()
llround[fl]()
trunc[fl]()
fmod[fl]()
remainder[fl]()
remquo[fl]()
copysign[fl]()
nan[fl]()
nextafter[fl]()
nexttoward[fl]()
fdim[fl]()
fmax[fl]()
fmin[fl]()
fma[fl]()

setjmp.h
Types:
jmp_buf

Function-like Macros:
setjmp()

Functions:
longjmp()

signal.h
Types:
sig_atomic_t

Macros:
SIG_DFL
SIG_ERR
SIG_IGN
SIGABRT
SIGFPE
SIGILL
SIGINT
SIGSEGV
SIGTERM

Functions:
🟣 signal()      Recommended to use POSIX sigaction() instead unless you can't or have a specific reason to use signal()
raise()

stdarg.h
🟣Definitions to know:
"TYPE" here can refer to any type known at the execution of this code including simple types such as: int, float, long long, etc. as well as advanced types and user-defined types.
"ellipsis" is the "..." used in the function paramater list to denote that this function can take variable number of arguments.

Types:
va_list

Function-like macros:
va_arg()
va_start()
va_end()
va_copy()

stddef.h
Types:
max_align_t
ptrdiff_t
size_t
wchar_t

Macros:
NULL

Function-like Macros:
offsetof()

stdio.h
Types:
size_t
FILE✅
fpos_t

Macros:
NULL
EOF
FOPEN_MAX
FILENAME_MAX
stdin
stdout
stderr
putc()
putchar()

🟣 For use with the setvbuf() function:
_IOFBF
_IOLBF
_IONBF

🟣 For use with the setbuf() function:
BUFSIZ

🟣 For use with the tmppnam() function:
L_tmpnam
TMP_MAX

🟣 For use with the fseek() function:
SEEK_CUR
SEEK_END
SEEK_SET

Other Functions:
clearerr()
fclose()✅
feof()
ferror()
fflush()✅
fgetc()
fgetpos()✅
fgets()🟧✅
fopen()✅
fputc()
fputs()
fread()✅
freopen()
fseek()✅
ftell()✅
fwrite()✅
getc()✅
getchar()
gets()❌
fsetpos()✅
perror()
*printf()✅
puts()✅
remove()
rename()
rewind()✅
*scanf()🟧
setbuf()
setvbuf()✅
tmpfile()
tmpnam()
ungetc()

stdlib.h
Types:
size_t
wchar_t
div_t
ldiv_t
lldiv_t

Macros:
NULL
EXIT_FAILURE
EXIT_SUCCESS
RAND_MAX
MB_CUR_MAX

Functions:
❌ atof()
❌ atoi()
❌ atol()
❌ atoll()
strtod()
strtof()
strtold()
strtol()
strtoll()
strtoul()
✅strtoull()
🟧 rand()
🟧 srand()
aligned_alloc()
✅calloc()
✅free()
✅malloc()
✅realloc()
abort()
atexit()
at_quick_exit()
✅exit()
_Exit()
getenv()
quick_exit()
✅system()
bsearch()
qsort()
✅abs()
labs()
llabs()
div()
ldiv()
lldiv()
mblen()
mbtowc()
wctomb()
mbstowcs()
wcstombs()

string.h
Types:
size_t

Macros:
NULL

Strings are simply an array of characters that are appended with a null terminating '\0' character at the end of it. (not necessarily the last element of the array)
Simply put, if there is no null terminating character at the index following the end of the text within the size of the array, then it is not a string and should not be handled like one.
The most common errors in C are string related errors because people often overwrite, truncate, or forget to copy over the null terminating character at the end of strings.

Functions:
memchr()
memcmp()
memcpy()
memmove()
memset()
strcat()
strchr()
strcmp()
strcoll()
strcpy()
strcspn()
strerror()
strlen()
strncat()
strncmp()
strncpy()
strpbrk()
strrchr()
strspn()
strstr()
strtok()
strxfrm()

time.h
Macros:
NULL
CLOCKS_PER_SEC
TIME_UTC

Types:
size_t
clock_t
time_t
struct tm
struct timespec

Functions:
clock()
difftime()
mktime()
time()
timespec_get()
🟧 asctime()
🟧 ctime()
🟧 gmtime()
🟧 localtime()
strftime()

C95 headers:
iso646.h
and becomes &&
and_eq becomes &=
bitand becomes &
bitor becomes |
compl becomes ~
not becomes !
not_eq becomes !=
or becomes ||
or_eq becomes |=
xor becomes ^
xor_eq becomes ^=
wchar.h
Types:
wchar_t
size_t
mbstate_t
wint_t
struct tm

Macros:
WEOF
WCHAR_MIN
WCHAR_MAX

Functions:
*wprintf()
*wscanf()
fgetwc()
fgetws()
fputwc()
fputws()
fwide()
getwc()
getwchar()
putwc()
putwchar()
ungetwc()
wcsto*()
wcscpy()
wcsncpy()
wmemcpy()
wmemmove()
wcscat()
wcsncat()
wcscmp()
wcscoll()
wcsncmp()
wcsxfrm()
wmemcmp()
wcschr()
wcscspn()
wcspbrk()
wcsrchr()
wcsspn()
wcsstr()
wcstok()
wmemchr()
🟧 wcslen()
wmemset()
wcsftime()
btowc()
wctob()
mbsinit()
mbrlen()
mbrtowc()
wcrtomb()
mbsrtowcs()
wcsrtombs()

wctype.h
Types:
wint_t
wctrans_t
wctype_t

Macros:
WCHAR_MIN WCHAR_MAX WEOF

Functions:
iswalnum()
iswalpha()
iswblank()
iswcntrl()
iswdigit()
iswgraph()
iswlower()
iswprint()
iswpunct()
iswspace()
iswupper()
iswxdigit()
iswctype()
wctype()
towlower()
towupper()
towctrans()
wctrans()

C99 headers:
complex.h (optional header)
Pragmas:
STDC CX_LIMITED_RANGE

Macros:
complex
_Complex_I
imaginary
_Imaginary_I
I

Function-like Macros:
CMPLX[FL]()

Functions:
cabs[fl]()
cacos[fl]()
cacosh[fl]()
carg[fl]()
casin[fl]()
casinh[fl]()
catan[fl]()
catanh[fl]()
ccos[fl]()
ccosh[fl]()
cexp[fl]()
cimag[fl]()
conj[fl]()
cpow[fl]()
cproj[fl]()
creal[fl]()
csin[fl]()
csinh[fl]()
csqrt[fl]()
ctan[fl]()
ctanh[fl]()

fenv.h
Pragmas:
STDC FENV_ACCESS

Types: fenv_t
fexcept_t

Macros:
FE_DIVBYZERO
FE_INEXACT
FE_INVALID
FE_OVERFLOW
FE_UNDERFLOW
FE_ALL_EXCEPT
FE_DOWNWARD
FE_TONEAREST
FE_TOWARDZERO
FE_UPWARD
FE_DFL_ENV

Functions:
feclearexcept()
fegetexceptflag()
feraiseexcept()
fesetexceptflag()
fetestexcept()
fegetround()
fesetround()
fegetenv()
feholdexcept()
fesetenv()
feupdateenv()

inttypes.h
Everything below with a "[8,16,32,64]" means that there are four functions of that base that have either an 8, 16, 32 or 64 appended to the end of the function respectively.
Other headers:
stdint.h

Types:
imaxdiv_t

Macros:
PRIx[8,16,32,64]
PRIxFAST[8,16,32,64]
PRIxLEAST[8,16,32,64]
PRIxMAX
PRIxPTR
PRId[8,16,32,64]
PRIdFAST[8,16,32,64]
PRIdLEAST[8,16,32,64]
PRIdMAX
PRIdPTR
PRIi[8,16,32,64]
PRIiFAST[8,16,32,64]
PRIiLEAST[8,16,32,64]
PRIiMAX
PRIiPTR
PRIo[8,16,32,64]
PRIoFAST[8,16,32,64]
PRIoLEAST[8,16,32,64]
PRIoMAX
PRIoPTR
PRIu[8,16,32,64]
PRIuFAST[8,16,32,64]
PRIuLEAST[8,16,32,64]
PRIuMAX
PRIuPTR
PRIx[8,16,32,64]
PRIxFAST[8,16,32,64]
PRIxLEAST[8,16,32,64]
PRIxMAX
PRIxPTR
SCNd[8,16,32,64]
SCNdFAST[8,16,32,64]
SCNdLEAST[8,16,32,64]
SCNdMAX
SCNdPTR
SCNi[8,16,32,64]
SCNiFAST[8,16,32,64]
SCNiLEAST[8,16,32,64]
SCNiMAX
SCNiPTR
SCNo[8,16,32,64]
SCNoFAST[8,16,32,64]
SCNoLEAST[8,16,32,64]
SCNoMAX
SCNoPTR
SCNu[8,16,32,64]
SCNuFAST[8,16,32,64]
SCNuLEAST[8,16,32,64]
SCNuMAX
SCNuPTR
SCNx[8,16,32,64]
SCNxFAST[8,16,32,64]
SCNxLEAST[8,16,32,64]
SCNxMAX
SCNxPTR

Functions:
imaxabs()
imaxdiv()
strtoimax()
strtoumax()
wcstoimax()
wcstoumax()

stdbool.h
Macros:
__bool_true_false_are_defined
bool
false
true

stdint.h
Types:
[,u]int_least[8,16,32,64]_t
[,u]int_fast[8,16,32,64]_t
🟣 [,u]intptr_t (optional)
[,u]intmax_t

🟣Below is optional where the "[VALUE]" can be replaced by any supported numbers less than or equal to N (usually 8, 16, 32, and 64)
[,u]int[VALUE]_t

Function-like macros:
🟣Any "[VALUE]" in the identifier name below is to be replaced by any supported numbers less than or equal to N (usually 8, 16, 32, and 64)
[,U]INTMAX_C()
[,U]INT[VALUE]_C

Macros:
INTMAX_MIN
[,U]INTMAX_[MAX,WIDTH]
[,U]INTPTR_[MAX,WIDTH]
INTPTR_MIN
PTRDIFF_[MIN,MAX]
SIGATOMIC_[MIN,MAX]
SIZE_MAX
WCHAR_[MIN,MAX]
WINT_[MIN,MAX]

🟣Everything listed below is optional. The "[VALUE]" is to be replaced by any supported numbers less than or equal to N (usually 8, 16, 32, and 64)
INT[VALUE]_MIN
INT_FAST[VALUE]_MIN
INT_LEAST[VALUE]_MIN
[,U]INT[VALUE]_[MAX,WIDTH]
[,U]INT_FAST[VALUE]_[MAX,WIDTH]
[,U]INT_LEAST[VALUE]_[MAX,WIDTH]

tgmath.h
Includes <math.h> and <complex.h> when it is called. Is a very useful header in that it is kindof like a generic handler for all math functions and determines the best function to call depending on the arguments/variable types passed into the functions. So if you use the sqrt() function with normal numbers it will use the <math.h> sqrt() function, but if you passed a complex number to it then it will use the appropriate function from <complex.h> instead.

C11 headers:
stdalign.h
Macros:
alignas
alignof
__alignas_is_defined
__alignof_is_defined

stdatomic.h (optional header)
🟣RETURN types to know:
An A refers to an atomic types.
A C refers to its corresponding non-atomic type.
An M refers to the type of the other argument for arithmetic operations.
For atomic integer types, M is C. For atomic pointer types, M is ptrdiff_t.

Macros:
ATOMIC_BOOL_LOCK_FREE
ATOMIC_CHAR_LOCK_FREE
ATOMIC_CHAR*_T_LOCK_FREE
ATOMIC_WCHAR_T_LOCK_FREE
ATOMIC_SHORT_LOCK_FREE
ATOMIC_INT_LOCK_FREE
ATOMIC_*LONG_LOCK_FREE
ATOMIC_POINTER_LOCK_FREE
ATOMIC_FLAG_INIT

Types:
enum memory_order
struct atomic_flag

All of the following types just expand to themselves preceeded with an _Atomic
atomic_bool
atomic_*char
atomic_*short
atomic_*int
atomic_*long
atomic_*llong
atomic_*int_least*
atomic_*int_fast*
atomic_*intptr_t
atomic_*intmax_t
atomic_char*_t
atomic_wchar_t
atomic_size_t
atomic_ptrdiff_t

Function-like Macros:
ATOMIC_VAR_INIT()
kill_dependency()

Functions:
atomic_init()
atomic_thread_fence()
atomic_signal_fence()
atomic_is_lock_free()
atomic_store*
atomic_load*
atomic_exchange*
atomic_compare_exchange_strong*
atomic_compare_exchange_weak*
atomic_flag_test_and_set*
atomic_flag_clear*
atomic_fetch*

stdnoreturn.h (Controversial lol)
Macros:
noreturn

🟣 threads.h (Optional header)
Other headers:
time.h

Types:
cnd_t
thrd_t
tss_t
mtx_t
tss_dtor_t
thrd_start_t
once_flag
mtx_plain
mtx_recursive
mtx_timed
thrd_timeout
thrd_success
thrd_busy
thrd_error
thrd_nomem

Macros:
thread_local
ONCE_FLAG_INIT
TSS_DTOR_ITERATIONS

Functions:
call_once()
cnd_broadcast()
cnd_destroy()
cnd_init()
cnd_signal()
cnd_timedwait()
cnd_wait()
mtx_destroy()
mtx_init()
mtx_lock()
mtx_timedlock()
mtx_trylock()
mtx_unlock()
thrd_create()
thrd_current()
thrd_detach()
thrd_equal()
thrd_exit()
thrd_join()
thrd_sleep()
thrd_yield()
tss_create()
tss_delete()
tss_get()
tss_set()

uchar.h
Types:
mbstate_t
char16_t
char32_t

Functions:
mbrtoc16()
c16rtomb()
mbrtoc32()
c32rtomb()


Other Important Libraries
ncurses.h
More official documentation.

NCURSES Types:
attr_t
cchar_t
wint_t
wchar_t
chtype
bool
WINDOW
SCREEN

General Macros:
COLS
LINES
curscr
stdscr
EOF
ERR
FALSE
OK
TRUE
WEOF
_XOPEN_CURSES

attr_t Manipulation Macros:
WA_ALTCHARSET
WA_BLINK
WA_BOLD
WA_DIM
WA_HORIZONTAL
WA_INVIS
WA_LEFT
WA_LOW
WA_PROTECT
WA_REVERSE
WA_RIGHT
WA_STANDOUT
WA_TOP
WA_UNDERLINE
WA_VERTICAL

chtype Manipulation Macros:
A_ALTCHARSET
A_BLINK
A_BOLD
A_DIM
A_INVIS
A_PROTECT
A_REVERSE
A_STANDOUT
A_UNDERLINE

chtype Extraction Macros:
A_ATTRIBUTES
A_CHARTEXT
A_COLOR

Alternative Character Set (ACS) Macros:
[w] is for wide-character alternative for the macro
[W]ACS_BLOCK
[W]ACS_BOARD
[W]ACS_BTEE
[W]ACS_BULLET
[W]ACS_CKBOARD
[W]ACS_DARROW
[W]ACS_DEGREE
[W]ACS_DIAMOND
[W]ACS_HLINE
[W]ACS_LANTERN
[W]ACS_LARROW
[W]ACS_LLCORNER
[W]ACS_LRCORNER
[W]ACS_LTEE
[W]ACS_PLMINUS
[W]ACS_PLUS
[W]ACS_RARROW
[W]ACS_RTEE
[W]ACS_S1
[W]ACS_S9
[W]ACS_TTEE
[W]ACS_UARROW
[W]ACS_ULCORNER
[W]ACS_URCORNER
[W]ACS_VLINE

Color-related Macros:
"COLOUR" alternatives available
COLOR_PAIRS
COLORS
COLOR_BLACK
COLOR_BLUE
COLOR_GREEN
COLOR_CYAN
COLOR_RED
COLOR_MAGENTA
COLOR_YELLOW
COLOR_WHITE

Symbolic Function Key Macros:
KEY_CODE_YES
KEY_BREAK
KEY_DOWN
KEY_UP
KEY_LEFT
KEY_RIGHT
KEY_HOME
KEY_BACKSPACE
KEY_F0
KEY_F(n)
KEY_DL
KEY_IL
KEY_DC
KEY_IC
KEY_EIC
KEY_CLEAR
KEY_EOS
KEY_EOL
KEY_SF
KEY_SR
KEY_NPAGE
KEY_PPAGE
KEY_STAB
KEY_CTAB
KEY_CATAB
KEY_ENTER
KEY_SRESET
KEY_RESET
KEY_PRINT
KEY_LL
KEY_A1
KEY_A3
KEY_B2
KEY_C1
KEY_C3

KEY_BTAB
KEY_BEG
KEY_CANCEL
KEY_CLOSE
KEY_COMMAND
KEY_COPY
KEY_CREATE
KEY_END
KEY_EXIT
KEY_FIND
KEY_HELP
KEY_MARK
KEY_MESSAGE
KEY_MOVE
KEY_NEXT
KEY_OPEN
KEY_OPTIONS
KEY_PREVIOUS
KEY_REDO
KEY_REFERENCE
KEY_REFRESH
KEY_REPLACE
KEY_RESTART
KEY_RESUME
KEY_SAVE
KEY_SBEG
KEY_SCANCEL
KEY_SCOMMAND
KEY_SCOPY
KEY_SCREATE
KEY_SDC
KEY_SDL
KEY_SELECT
KEY_SEND
KEY_SEOL
KEY_SEXIT
KEY_SFIND
KEY_SHELP
KEY_SHOME
KEY_SIC
KEY_SLEFT
KEY_SMESSAGE
KEY_SMOVE
KEY_SNEXT
KEY_SOPTIONS
KEY_SPREVIOUS
KEY_SPRINT
KEY_SREDO
KEY_SREPLACE
KEY_SRIGHT
KEY_SRSUME
KEY_SSAVE
KEY_SSUSPEND
KEY_SUNDO
KEY_SUSPEND
KEY_UNDO

Coordinate-related Function-like Macros:
getmaxyx()
getbegyx()
getparyx()
getyx()

NOTE: Unless otherwise specified by listing all of the functions individually next to each other the following is true:
[mv] adds "int, int" to the paramaters list denoting the y and x coordinates respectfully
[w] adds a "WINDOW*" to the paramaters list to limit the action for a specific window
When used in conjunction the first paramater is always the "WINDOW*" while the second and third arguments are "int, int"
Any remaining paramaters are those specific to the root function.

Color-related Functions:
can_change_color()
color_content()
has_colors()
COLOR_PAIR()
init_color()
init_pair()
pair_content()
PAIR_NUMBER()
start_color()
[w]color_set()

Atrribute and Color Manipulation Functions:
[w]attr_get()
[w]attr_off()
[w]attr_on()
[w]attr_set()
[w]attroff()
[w]attron()
[w]attrset()

Manipulates the set of soft function-key labels:
slk_attr_off()
slk_attr_on()
slk_attr_set()
slk_attroff()
slk_attron()
slk_attrset()
slk_clear()
slk_color()
slk_init()
slk_label()
slk_noutrefresh()
slk_refresh()
slk_restore()
slk_set()
slk_touch()
slk_wset()

Functions dealing with background attributes:
getbkgd()
[w]bkgd()
[w]bkgdset()
[w]bkgrnd()
[w]bkgrndset()
[w]getbkgrnd()

Functions to clear the whole or part of the screen or window:
[w]clear()
clearok()
clrtobot()
clrtoeol()

Functions to save or store terminal modes:
def_prog_mode()
def_shell_mode()
reset_prog_mode()
reset_shell_mode()

Grouped Miscellaneous Functions:
unget_wch()
ungetch()
has_ic()
has_il()
tigetstr()
tigetflag()
tigetnum()
tparm()
ripoffline()
is_linetouched()
idcok()
idlok()
immedok()
key_name()
keyname()
term_attrs()
termattrs()
scrl()
scroll()
scrollok()
wscrl()
resetty()
savetty()
touchline()
wtouchln()
touchwin()
untouchwin()
vid_attr()
vid_puts()
vidattr()
vidputs()
syncok()
wcursyncup()
wsyncdown()
wsyncup()
scr_dump()
scr_init()
scr_restore()
scr_set()
[w]standend()
[w]standout()
pnoutrefresh()
prefresh()
refresh()
wnoutrefresh()
wrefresh()
cbreak()
nocbreak()
echo()
noecho()
nl()
nonl()
raw()
noraw()
qiflush()
noqiflush()
[w]border()
[w]border_set()
[w]deleteln()
derwin()
mvderwin()
[w]erase()
erasechar()
erasewchar()
[w]insdelln()
[w]insertln()
[w]move()
[w]setscrreg()
[p,w]echo_wchar()
[p,w]echochar()
[w]timeout()
notimeout()
vw_printw()
vwprintw()
vw_scanw()
vwscanw()
wclrtobot()
wclrtoeol()

General One Off Functions:
baudrate()
beep()
box()
box_set()
copywin()
curs_set()
delay_output()
delscreen()
delwin()
doupdate()
dupwin()
endwin()
filter()
flash()
flushinp()
getcchar()
getwin()
halfdelay()
initscr()
intrflush()
is_wintouched()
isendwin()
keypad()
killchar()
killwchar()
leaveok()
longname()
meta()
mvcur()
mvwin()
napms()
newpad()
newterm()
newwin()
nodelay()
overlay()
overwrite()
putp()
putwin()
redrawwin()
set_term()
setcchar()
setupterm()
subpad()
subwin()
termname()
typeahead()
use_env()
wredrawln()
wunctrl()

Functions with Move [mv] and Window [w] Extensions:
[mv][w]add_wch()
[mv][w]add_wchnstr()
[mv][w]add_wchstr()
[mv][w]addch()
[mv][w]addchnstr()
[mv][w]addchstr()
[mv][w]addnstr()
[mv][w]addnwstr()
[mv][w]addstr()
[mv][w]addwstr()
[mv][w]chgat()
[mv][w]delch()
[mv][w]get_wch()
[mv][w]get_wstr()
[mv][w]getch()
[mv][w]getn_wstr()
[mv][w]getnstr()
[mv][w]getstr()
[mv][w]hline()
[mv][w]hline_set()
[mv][w]in_wch()
[mv][w]in_wchnstr()
[mv][w]in_wchstr()
[mv][w]inch()
[mv][w]inchnstr()
[mv][w]inchstr()
[mv][w]innstr()
[mv][w]innwstr()
[mv][w]ins_nwstr()
[mv][w]ins_wch()
[mv][w]ins_wstr()
[mv][w]insch()
[mv][w]insnstr()
[mv][w]insstr()
[mv][w]instr()
[mv][w]inwstr()
[mv][w]printw()
[mv][w]scanw()
[mv][w]vline()
[mv][w]vline_set()

menu.h (a.k.a libmenu)
Other headers:
eti.h

Which includes the following macros for errors that are returned by functions:
E_OK
E_SYSTEM_ERROR
E_BAD_ARGUMENT
E_POSTED
E_CONNECTED
E_BAD_STATE
E_NO_ROOM
E_NOT_POSTED
E_UNKNOWN_COMMAND
E_NO_MATCH
E_NOT_SELECTABLE
E_NOT_CONNECTED
E_REQUEST_DENIED
E_INVALID_FIELD
E_CURRENT

Types:
TEXT
ITEM
MENU
Menu_Options
Item_Options

Macros:
O_ONEVALUE
O_SHOWDESC
O_ROWMAJOR
O_IGNORECASE
O_SHOWMATCH
O_NONCYCLIC
O_SELECTABLE
REQ_LEFT_ITEM
REQ_RIGHT_ITEM
REQ_UP_ITEM
REQ_DOWN_ITEM
REQ_SCR_ULINE
REQ_SCR_DLINE
REQ_SCR_DPAGE
REQ_SCR_UPAGE
REQ_FIRST_ITEM
REQ_LAST_ITEM
REQ_NEXT_ITEM
REQ_PREV_ITEM
REQ_TOGGLE_ITEM
REQ_CLEAR_PATTERN
REQ_BACK_PATTERN
REQ_NEXT_MATCH
REQ_PREV_MATCH
MIN_MENU_COMMAND
MAX_MENU_COMMAND
MAX_COMMAND

🟣 Sometimes "Item_Options" and Menu_Options are both referred to as just "OPTIONS" in some documentation sources.
Functions:
current_item()
free_item()
free_menu()
item_count()
item_description()
item_index()
item_init()
item_name()
item_opts()
item_opts_off()
item_opts_on()
item_term()
item_userptr()
item_value()
item_visible()
menu_back()
menu_driver()
menu_fore()
menu_format()
menu_grey()
menu_init()
menu_items()
menu_mark()
menu_opts()
menu_opts_off()
menu_opts_on()
menu_pad()
menu_pattern()
menu_request_by_name()
menu_request_name()
menu_spacing()
menu_sub()
menu_term()
menu_userptr()
menu_win()
new_item()
new_menu()
pos_menu_cursor()
post_menu()
scale_menu()
set_current_item()
set_item_init()
set_item_opts()
set_item_term()
set_item_userptr()
set_item_value()
set_menu_back()
set_menu_fore()
set_menu_format()
set_menu_grey()
set_menu_init()
set_menu_items()
set_menu_mark()
set_menu_opts()
set_menu_pad()
set_menu_pattern()
set_menu_spacing()
set_menu_sub()
set_menu_term()
set_menu_userptr()
set_menu_win()
set_top_row()
top_row()
unpost_menu()
panel.h (a.k.a libpanel)
Types:
PANEL

Functions:
bottom_panel()
del_panel()
hide_panel()
move_panel()
*new_panel()
*panel_above()
*panel_below()
panel_hidden()
*panel_userptr()
*panel_window()
replace_panel()
set_panel_userptr()
show_panel()
top_panel()
update_panels()
form.h (a.k.a. libform)
Other headers:
eti.h

Which includes the following macros for errors that are returned by functions:
E_OK
E_SYSTEM_ERROR
E_BAD_ARGUMENT
E_POSTED
E_CONNECTED
E_BAD_STATE
E_NO_ROOM
E_NOT_POSTED
E_UNKNOWN_COMMAND
E_NO_MATCH
E_NOT_SELECTABLE
E_NOT_CONNECTED
E_REQUEST_DENIED
E_INVALID_FIELD
E_CURRENT

Types:
🟣 NOTE: sometimes Form_Hook is simply referred to as HOOK in some implementations
FIELD_CELL
Form_Options
Field_Options
_PAGE
FIELD
FORM
FIELDTYPE
TYPE_ALPHA
TYPE_ALNUM
TYPE_ENUM
TYPE_INTEGER
TYPE_NUMERIC
TYPE_REGEXP
TYPE_IPV4
Form_Hook

Macros:
FORM_IMPEXP
FORM_WRAPPED_VAR(type,name)
FORM_EXPORT(type)
FORM_EXPORT_VAR(type)
NCURSES_FIELD_INTERNALS
NO_JUSTIFICATION
JUSTIFY_LEFT
JUSTIFY_CENTER
JUSTIFY_RIGHT
O_VISIBLE
O_ACTIVE
O_PUBLIC
O_EDIT
O_WRAP
O_BLANK
O_AUTOSKIP
O_NULLOK
O_PASSOK
O_STATIC
O_DYNAMIC_JUSTIFY
O_NO_LEFT_STRIP
O_EDGE_INSERT_STAY
O_INPUT_LIMIT
O_NL_OVERLOAD
O_BS_OVERLOAD
REQ_NEXT_PAGE
REQ_PREV_PAGE
REQ_FIRST_PAGE
REQ_LAST_PAGE
REQ_NEXT_FIELD
REQ_PREV_FIELD
REQ_FIRST_FIELD
REQ_LAST_FIELD
REQ_SNEXT_FIELD
REQ_SPREV_FIELD
REQ_SFIRST_FIELD
REQ_SLAST_FIELD
REQ_LEFT_FIELD
REQ_RIGHT_FIELD
REQ_UP_FIELD
REQ_DOWN_FIELD
REQ_NEXT_CHAR
REQ_PREV_CHAR
REQ_NEXT_LINE
REQ_PREV_LINE
REQ_NEXT_WORD
REQ_PREV_WORD
REQ_BEG_FIELD
REQ_END_FIELD
REQ_BEG_LINE
REQ_END_LINE
REQ_LEFT_CHAR
REQ_RIGHT_CHAR
REQ_UP_CHAR
REQ_DOWN_CHAR
REQ_NEW_LINE
REQ_INS_CHAR
REQ_INS_LINE
REQ_DEL_CHAR
REQ_DEL_PREV
REQ_DEL_LINE
REQ_DEL_WORD
REQ_CLR_EOL
REQ_CLR_EOF
REQ_CLR_FIELD
REQ_OVL_MODE
REQ_INS_MODE
REQ_SCR_FLINE
REQ_SCR_BLINE
REQ_SCR_FPAGE
REQ_SCR_BPAGE
REQ_SCR_FHPAGE
REQ_SCR_BHPAGE
REQ_SCR_FCHAR
REQ_SCR_BCHAR
REQ_SCR_HFLINE
REQ_SCR_HBLINE
REQ_SCR_HFHALF
REQ_SCR_HBHALF
REQ_VALIDATION
REQ_NEXT_CHOICE
REQ_PREV_CHOICE
MIN_FORM_COMMAND
MAX_FORM_COMMAND
MAX_COMMAND

🟣 NOTE: Some of the functions may have "int" return or paramater types that in other implementations might be "bool" types, and some of the functions that have "char*" might be defined as "void*" in other implementations.
🟣 NOTE: Some implementations might call Field_Options as OPTIONS.
Functions:
new_fieldtype()
link_fieldtype()
free_fieldtype()
set_fieldtype_arg()
set_fieldtype_choice()
new_field()
dup_field()
link_field()
free_field()
field_info()
dynamic_field_info()
move_field()
set_field_type()
set_new_page()
new_page()
set_field_just()
field_just()
set_field_fore()
set_field_back()
set_field_pad()
field_pad()
set_field_buffer()
field_buffer()
set_field_status()
field_status()
set_max_field()
set_field_userptr()
field_userptr()
set_field_opts()
field_opts_on()
field_opts_off()
field_opts()
field_fore()
field_back()
field_arg()
field_type()
set_form_fields()
form_fields()
field_count()
new_form()
free_form()
current_field()
set_form_page()
form_page()
set_current_field()
field_index()
set_form_win()
form_win()
set_form_sub()
form_sub()
scale_form()
form_init()
form_term()
field_init()
field_term()
set_form_init()
set_form_term()
set_field_init()
set_field_term()
unfocus_current_field()
post_form()
unpost_form()
pos_form_cursor()
form_driver()
form_driver_w()
set_form_userptr()
form_userptr()
set_form_opts()
form_opts_on()
form_opts_off()
form_opts()
data_ahead()
data_behind()
form_request_by_name()
form_request_name()



POSIX Standard Library
cppreference.com's List of Open Source C Libraries
Oz Tiram (oz123)'s "awesome-c" Github.com repo- a curated list of Libraries (some duplicates)
Microsoft's Window's API (Win32)
About C
Objective facts incoming-> C is an: "old, middle-aged, general-purpose, multi-paradigm, imperative, procedural, structured, low and high level, manifest, statically, nominally, strongly and weakly typed, cross-platform, platform-specific, compiled programming language." Depending on whom you ask. As you can tell, a lot of those groups contradict one another. This is due to the fact that like most things in the programming world, it really depends on who you ask and what the standard you are comparing it too is. C's common uses are for operating and embedded systems, but it is used in much more than just those areas. C is an older language (but not the oldest) and with this age comes a lot of great things including tons of references and resources online as well as loads of libraries for doing anything you could ever imagine. Its age is not all good though, as it has lead to tons of problems as it has aged as now lots of features that are essential in programming in present times were not conceived of back when it was created so in order to add features that were so essential to so many people it had to use the bolted-on approach meaning that many things like strings, threads and other not first-class types are extra difficult and error prone to use.

C Implementations
An implementation of the C programming language is providing all the necessary tools to write and execute a program in C. There are three general types of implementations in C that are discussed a little later in this document. They are hosted, freestanding and non-conforming implementations. K&R C - The first implementation of C. This is not a real implementation and is a general catch-all term for every iteration of C prior to it being standardized in 1989. In the beginning there was Fortran…. Ken Thompson who worked at Bell Labs tried to write a Fortran Compiler. Being too complicated, he tried instead to make a simplified version of BCPL- which is another language. With inspiration from ALGOL and its derivative SMALGOL to aid him in determining the scope of the project. He later called this language B. Bell Labs gave Ken Thompson and his partner Dennis Ritchie a PDP-11 for their work which was a much more powerful computer. B did not have the ability to use this new power baked into the language so Dennis Ritchie began to modify it so that it would. Both were working on a bigger project at this time being UNIX. This was so they could break away from the standard Multics operating system that hamstrung them in so many ways. They wanted to make an operating system that was more portable. The portability idea is what really drove home the idea that they needed to create a language that could be ported over from one machine to another and still work. Thats what they did, creating Unix and writing it in his new B language. Ritchie had made such extensive changes to the language it was time to change its name and thus it became C. Five years later Ritchie as well as another Bell Labs employee Brian Kernighan who was an author/writer at Bell Labs, but also very capable technically wrote “The C Programming Language” which is by far the most popular C book in history. This book was the standard in a way until 1989 before formal lines had to be drawn and everything before this is known as K&R C. C89/ANSI- The first Standard of C was made, because the language had grown so much in popularity- it was necessary to create a standard to preserve any notion of being portable from one machine to another. ANSI made a standard so that there was at least some agreed upon rules for people to listen too. A year later they passed the project to another standardization organization ISO who then published: ISO C/C90. ISO still maintains the standard to this day. In 1995, 1999, 2011, and 2017/2018 more revisions to the language were made and thusly called C95, C99, C11 and C17/18 respectively. With most of the major changes being done at or before C99. Almost all of these changes between 1989 to present have been additive changes so that it would never break backwards compatible to any unsurmountable degree. C++ was originally a fork of C, and is often referred to as C with classes. It was created by Bjarne Stroustrup who worked just down the hallway from Ritchie and Kernighan at Bell Labs and originally worked very closely with them during the early years of his language. As the years went on, however, the languages have greatly divided themselves and many people consider them distant relatives at this point. It is often said that they require two different mindsets while coding and have quite opposite coding paradigms leading to lots of issues when programmers move in-between both implementations. They are now considered to different languages rather than a pure subset/superset relationship that many had originally wished for.

Hosted,Freestanding and Non-conforming Implementations
A hosted implementation is also freestanding by definition, but not visa versa. A group of people (currently ISO as of this writing) creates the “standard” for the C programming language. Which they provide for a fee from their site. Technically this means that the current C standard is proprietary, but as you might have read just above there is not just “one” implementation of C. This is both good and bad at the same time. While C is not beholden to any one or few parties to make all the decisions about the language that us peasants must conform too, it is simultaneously nice to have some sense of a standard so code on one machine or implementation can run or just needs slight modifications to run on a completely other implementation. A hosted implementation is one that includes the core language and all of the standard libraries set out by standards committees. Freestanding implementations get a lot more leeway and only have a subset of headers and functions they are required to implement to be considered compliant. If you can read the writing on the wall you can quickly see how this can get cumbersome, confusing and down right irritating for developers. But at least those writing the implementation get freedom. A list of common implementations of all sorts are: Libraries:(POSIX, BSD, Glibc, libc, newlib, uClibc, MSVCRT, nanolib, musl, klibc, Bionic libc, dietlibc, EGLIBC) Where libc is the ISO standard libraries and bare minimum for a hosted implementation. POSIX is a superset of that. BSD and Glibc are both supersets of POSIX. Compilers:(gcc, tiny c, clang, borland, PCC, MSVC).
Keywords
char
int
float
double
void

return
if
else
while
for
do
break
switch
case
default
continue
goto
enum
struct
union

long
short
unsigned
signed (default for int)

sizeof()
typedef

const
volatile
static
extern
register
❌ auto (obsolete)

restrict (C99)
inline (C99)
_Imaginary (C99)
_Bool (C99)
_Complex (C99)
_Alignas (C11)
_Alignof (C11)
_Atomic (C11)
_Generic (C11)
_Noreturn (C11)
_Static_assert (C11)
_Thread_local (C11)
C23 features to be added when finalized.
Operators
(&,*, and "" included twice because of differing meaning depending on context)

- + * / %
= ! > <
& | ^ ~
? :
( ) [ ] { }
. , ; * &
sizeof() _Alignof()

++ -- += -= *= /= %=
== != >= <=
&& || << >> <<= >>= &= ^= |= ->
" " ‘ ’ “ ”
Operator Precedence

Priority Operator Associativity
1st ()
[]
.
->
Left-to-right
2nd ++foo
--foo
+
-
!
~
(type)
*foo
&
sizeof()
_Alignof()
Right-to-left
3rd *
/
%
Left-to-right
4th +
-
Left-to-right
5th <<
>>
Left-to-right
6th <
>
<=
>=
Left-to-right
7th ==
!=
Left-to-right
8th & Left-to-right
9th ^ Left-to-right
10th | Left-to-right
11th && Left-to-right
12th || Left-to-right
13th () ? :
Right-to-left
14th =
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
Right-to-left
15th foo++
foo--
NA
16th , Left-to-right

Operations between variables and/or constants of different types have the same concept of promotion as listed under "types" in "Data in C". So operations on two types converts the lesser rank type to the greater rank type before conversion.

_Bool <= char <= short <= int <= void/pointer*(if allowed in implementation) <= long <= long long <= float <= double <= long double 

Operators Explained
Operators in C are the symbols you use to convey meaning to the compiler to do something to an operand just like in math. In C there is a plethora of operators and a few of them, that appear on the outside to do different things depending on their context. This is one of the primary hurdles people face as they are learning the language, because their are a few operators that context matters and a few that always do the same thing no matter where they are. Similarly to English how the meaning of a sentence can change drastically depending on the order of the words used. The list of operators is listed above, but now I am going to go through all of them one by one except for the bitwise operators which will be explained later. Note that this may be overlapping with information supplied in other areas of this document, but is used as a general guide of how to use the operators and what they do.
Note: Arithmetic operators on pointers are dependent on the type the var was declared to be pointing too.

- The ‘-’ operator is used as one would expect, it is used to subtract one number from another.
Example: int var = -6; int difference = var - 2; //difference would now hold the value -8
Note: while not use as an operator, the ‘-’ can also be used to denote negative numbers as per usual

+ The ‘+’ operator is also use just as one would in mathematics
Example: int var = 7; int sum = var + 42; //sum would be assigned to the value of 49

* The ‘*’ operator is used similar to most programs use it, it is used to to multiply one operand to another.
Example: int var = 3; int product = var * 7; //product was assigned to 21
Note: That the * can also be used in two other situations: during declaration of a variable which denotes that the variable is actually a pointer variable to a place in memory containing whatever the type is that it says. The other being the dereferencing operator, which tells the compiler to treat the value stored in the variable as an address to a location in memory and to either use or assign it depending on if it is used as an lvalue or rvalue.
Example: int a = 7; int* ptr = &foo; *foo = 8; //ptr now holds the address of foo and foo holds the value 8

/ The ‘/’ operator is also used as one would expect in that it denotes division between two operands.
Example: int var = 21; int divisor = var / 3; // divisor now holds the value 7

% The ‘%’ operator is often referred to as the modulus operator and is used to find the remainder after division of two operands. This is an extremely useful tool for many use cases like determining if a value is odd or even (% 2) does it return 1 or 0 determines if it is odd or even.
Example: int var 21; int modulus = var % 4; //modulus now holds the value 1. (4 goes into 21 five times with one left over)

= The ‘=’ operator is the bitwise assignment operator. It is NOT an equal sign. It has the highest precedence and assigns the value from the expression on the right into the variable on the left. How it works is it sees what bits are turned on and off on the value on the right (rvalue) and sets those same bits in the variable on the left (lvalue).
Ex. int var = 3; //var now holds the value 3

! The ‘!’ operator is the conditional NOT operator and is used to negate the value of the expression or operator immediately following it. It is often used in conditional statements like: != to mean not equal to.
Example: while (!var) { do something} // while the value of var is 0 “do something” in a loop

> The ‘>’ operator is the “greater than” sign and is used in conditional statements just as it is in math checking to see if the value on the left is greater than the value on the right and returns true/not 0 if it is true and false/0 if it is not true.
Example: if (1 > 3) //is false so this condition will return 0 and the code inside of the if block will not be executed.

< The ‘<’ operator is the inverse of the ‘>’ operator and works the exact same but checks to see that the lvalue is less than the rvalue.
Example: if (1 < 3) //is true so this condition will return 1 and the code inside of the if block will be executed.

& The ‘&’ operator is one of the trickiest in C, because it is used all the time and it does different things depending on the context. If it is used in from of a single operand than it returns the address of the operand (which must be a variable name) from the expression.
Ex. int a = 3; int* b = &a; //b now holds the address that a can be found at
The other thing it could mean if given two operands is the bitwise AND which looks at the specific bits of both operands and if they both mutual have any that are both 1 then it sets that bit to 1 and in all other cases it sets it to 0.
Ex. char a = 3; char b = a & 1; // b would now be 1 since the only bit the was on for both 3 and 1 was the 1s bit. 00000011 & 00000001 = 00000001
Ex 2. int a = 6 & 7; (6 = 00000110 and 7 = 00000111) = (00000110 6) thus the answer is 6;

| The ‘|’ operator is the binary/bitwise OR operator which like the bitwise/binary ‘&’ operator looks at the specific bits of two operands and if either one of the operands has a bit turned on in a byte it sets that bit to 1 in the value it returns.
Ex. char value = 3 | 4; // value is set to 7 ((3) 00000011 | 00000100 (4)) = (00000111) which is 7.
Ex2.char value = 3 | 5; //value is set to 7 as well

~ The ‘~’ operator is the "binary/bitwise complement” operator. It is used on a single operand and is used to “flip the bits” meaning it just takes all the bits that are set to 1 and makes them 0 and all the bits that were set to 0 and makes them 1.
Ex. char value = ~3; // value is set to -4 ((3) ~00000011) = (11111100) which is -4 if it is a signed int or 4,294,967,292 if it is unsigned (11111100).

^ The ‘^’ operator is the XOR a.k.a. exclusive or binary/bitwise operator. It returns a 1 if only one of its two operands has a bit turned on in that place and a 0 in every other case.
Ex. char value = 3 ^ 15; // value is set to 12 ((3) 00000011 ^ 00001111 (15)) = 00001100 (12). Ex2.char value = 3 ^ 5; //value is set to 6 ((3) 00000011 ^ 00000101 (5)) = 00000110 (12).

++ The ‘++’ operator is the prefix or postfix increment operator depending on whether it appears right before or after a variables name. This takes the variables value and adds one to it and stores the result back into the variable. While it appears to do the same thing regardless of postfix or prefix, when used during assignment it differs greatly. When used with the assignment operator '=', prefix occurs before assignment while postfix occurs after. This is explained more later in a section all about prefix and postfix operators.

-- The ‘--’ operator is the prefix or postfix decrement operator depending on whether it appears right before or after a variables name. This takes the variables value and subtracts one to it and stores the result back into the variable. While it appears to do the same thing regardless of postfix or prefix some of the time, when used during assignment it differs greatly. When used with the assignment operator '=', prefix occurs before assignment while postfix occurs after. This is explained more later in a section all about prefix and postfix operators.

== The '==' operator is the binary conditional/relational equality operator. It is used in conditionals to check that the left operand and the right operand's bits are identical. This works between most types as they just store numbers and thus a double variable and an int variable both containing 3 will return true if compared to one another. Remember that chars are also stored as numbers and thus 'A' and 'a' are not equivalent. Because of the tricky nature of strings and characters their are string functions in string.h to help compare strings to one another. If they are it returns non-zero (true) and if not it returns 0 (false).

!= The '!=' operator is the binary conditional/relational not-equality operator. It is the inverse of the equality operator and is used in conditionals to check that the left operand and the right operand's bits are subsequently NOT identical. This works between most types as they just store numbers and thus a double variable and an int variable both containing 3 will return false if compared to one another because it is not true that they are not equal. Remember that chars are also stored as numbers and thus 'A' and 'a' are not equal and thus would return true (not zero). Because of the tricky nature of strings and characters their are string functions in string.h to help compare strings to one another. If they are it returns non-zero (true) and if not it returns 0 (false).

>= The '>=' operator is the binary conditional/relational greater-than or equal operator that works the same as the > sign but returns true additionally if the left and right operand are equal to one another.

<= The '<=' operator is the binary conditional/relational less-than or equal operator that works the same as the < sign but returns true additionally if the left and right operand are equal to one another.

&& The '&&' operator is the logical AND operator and is used in conditional statements to check that the expression and the right and the left are BOTH true and if they are it returns true, but if either are false it returns false.

|| The '||' operator is the logical OR operator and is used in conditional statements to check that the expression or the right and the left are true and if they are it returns true if one or both of them are true, and false otherwise.

>> The '>>' operator is the binary RIGHT-SHIFT operator and is used to shift the bits of the left operand's bits to the right the number of times specified by the right operand. Adds zeros to the end as needed.

<< The '<<' operator is the binary LEFT-SHIFT operator and is used to shift the bits of the left operand's bits to the left the number of times specified by the right operand.Adds zeros to the end as needed.

. The '.' operator is the dot operator and is used to access members of unions and structs. To access the block of memory (to read or write) one just have to give the nameOfStruct.nameOfMember and then you can use it in expressions or assign a value to it like it was any other variable.

-> The '->' operator is the arrow operator is an operator that is used to access members of unions and structures when they have been passed by pointer. It is the equivalent of accessing a member of a struct or union regularly using the '.' operator. It is a short hand that prevents some awkward parentheses heavy pointer dereferences to access struct/union members.

' ' The ' ' operators are an operator that is used on a single constant letter, number and symbol and denotes to the compiler that you are referring to the human readable/English meaning of the character and not the 1's and 0's machine meaning of the symbol if there is one.
Ex. '9' can be used and printed to the screen as '9', while if you did 9 without the single quotes and tried to print it using a function like printf it would think that you meant to print a tab character since the tab character is at ASCII number 9.

" " The " " operator is the string operator and does one of two things depending on context. If used during the declaration of an array it fills the array with all the characters (converted to numbers) within the quotes. In all other cases it creates an array of characters that can be pointed to by a pointer. (this is often stored in static/read-only memory)
Ex1. char str[] = "Hello"; // This creates an array named str that stores H,e,l,l,o,0 in the stack in memory, if the array is large enough to store it.
Ex2. char* str = "Hello"; // This creates an unnamed array in static/global/(potentially read-only) memory that stores H,e,l,l,o,0 and is pointed to by str.
Basically the difference in the two situations is the same difference between pointers and array types.

sizeof() The 'sizeof()' operator is compile time operator that returns the space in storage that the result of an expression takes up. So it can be used to get the size of a type or returns the size that corresponds to what an expression would result in.
Ex1. sizeof(int); //implementation defined, but returns 4 on my machine. Ex2. sizeof(7+898); //implementation defined, but returns 4 again on my machine. Ex3. int a; double b; sizeof(a + b); //implementation defined, but returns 8 on my machine because the int is promoted to a double and the result of the expression is of type double which takes up 8 bytes on my machine. Ex4. int array[5]; sizeof(array); //implementation defined, but returns 20 on my machine because ints take up 4 bytes and the array type stores 5 elements of ints thus 4 * 5 = 20; (if array had already decayed into a pointer (for example if this was in a function outside of where the array was declared) the result on my machine would be 8 because all it would see is that it is a pointer and not a pointer to an array.


All of these operators: += -= *= /= %= <<= >>= &= ^= |=, do the same thing their non-assignment counterparts (without the equal '=' sign) do, but when they are done they store/assign the value in the left operand. (thus the left operand must be an lvalue (variable) and not just a number, because it has to know where in memory to store the answer.
= vs ==
‘=’ is the assignment operator and ‘==’ is the comparison/equality operator. In code when you see a single ‘=’ sign unless it is in a string literal then it is referring to assignment. This is not exactly like the equal sign in mathematics. x = 6; is not saying that x is equal to 6 exactly, more properly it is saying that the compiler should assign x to the value of 6. When testing expressions such as if statements, we often use the ‘==’ to check if one value is equal to another if it is true that they are equal to one another it returns a 1 and if not a 0 (c syntax for true and false). Take the following examples and determine what the value of y is:
int x = 2; int y = x; x = 4; // The value of 2 is stored in y, the value 4 is stored in x

int x = 3; int y = x; int x = y + 1; // The value of 3 is stored in y, the value of 4 is stored in x

int x = 2; int y = (3 == 3) + 4; // The value 5 is stored in y, x stores the value 2
This has to do with pass by value. (note this is implementation defined and should be avoided when possible)
Pre vs Post-increment (++var/--var, var++/var--)
Incrementing a variable means to assign the value of adding one to its current value while decrementing a variable means that to assign its value to the subtraction of one from its current value. Post and pre increment/decrement both act the same except for when being assigned. Pre incrementing/decrementing a variable takes precedence to assignment while post incrementing does not.

Example 1:
int x = 2; int y = ++x; (Y is now 3, and X is now 3) int y = x++; (Y is now 2, X is now 3)
Example 2:
int x = 2; int y = x--; (Y is now 2, X is now 1) int y = --x; (Y is now 1, X is now 1);
It should be noted that only one increment or decrement operator can safely be used in a single expression. It is undefined behavior to have multiple pre and/or post increment/decrement operators in a single expression. The reason for this is simply because it has no algebraic/mathematical replacement and thus the compiler has to decide which increment and decrement operation is performed first.

Example 3:
int i=4; printf("%d", ++i + ++i + ++i); // Prints 18 to the screen on my computer
Example 4:
int i=4;      int x = ++i + ++i + ++i; printf("%d", x); // Prints 23 to the screen on my computer
'' and "" operators
In c ‘a’ converts the human representation of   a   to its ASCII value (which is 97). The “” operator takes all of the human readable letters and converts them to their corresponding ASCII code points and also adds a null terminator (a.k.a. ASCII 0) to the very end of the string. So if you create a character array and set it equal to a string literal “”.

char array[5] = “Clay”;

char array[5] = {‘C’,’l’,’a’,’y’,’\0’};

char array[5] = {67,108,97,121,0};

All three of these examples are equivalent.

Note in all of these cases you don't need to include the five as the compiler is smart enough to put that there itself. In fact you probably shouldn't because if you later change the string to be larger than the number you put in there it will trim off the excess including the null terminator meaning that some functions will not know when to stop reading the string and will give undefined behavior.
"" Operator
The “” operators is a difficult operator to understand, because it appears to do different things depending on the context in which it is found. When INITIALIZING a character array it can be used to fill the array with the corresponding ASCII characters AND adds a NULL terminator to the string.

Example:
char arr[] = “C Programming”;
is equivalent to:
char arr[] = {‘C’,’ ’,’P’,’r’,’o’,’g’,’r’,’a’,’m’,’m’,’i’,’n’,’g’,’\0’};
and also:
char arr[14];
char arr[0] = ‘C’;
char arr[1] = ‘ ’;
char arr[2] = ‘P’;
char arr[3] = ‘r’;
char arr[4] = ‘o’;
char arr[5] = ‘g’;
char arr[6] = ‘r’;
char arr[7] = ‘a’;
char arr[8] = ‘m’;
char arr[9] = ‘m’;
char arr[10] = ‘i’;
char arr[11] = ‘n’;
char arr[12] = ‘g’;
char arr[13] = 0;

Outside of initialization, only pointers can be assigned to a “string”. This is because the “string” is really creating a block of memory (array) containing the characters int he string (usually in static, sometimes in read only memory depending on implementation). And this is why arrays cannot be assigned to strings because if you recall under the array section, arrays cannot be assigned to other arrays, because when they are used as an rvalue they “decay” into just pointers. So essentially when you are trying to assign an array to another, you are really just trying to assign the address of one array to the first element of another which causes a compilation error because arrays can only be assigned using {} notation or “” notation. You cannot assign a single element.
int myArr[] = 6; //ERROR, cannot assign to value using this notation
int arr2[20] = arr; //ERROR, cannot assign to value using this notation
char name[10];
name = “Clay”; //ERROR, can only be INITIALIZED with a string, cannot be ASSIGNED to string

To sum it up, basically the compiler is usually smart enough to know that when you are initializing an array to a string that you want the array to be assigned to the corresponding numbers/characters in the string and not just be sent a pointer to a location in static memory to the string. Everywhere else however, it DOES assume that you just want a pointer to the location in static memory where “the string is stored”. This is really really confusing and I am not sure if I explained this well enough. I will probably come back through a later time and try to revise this heavily to make it more clear as this is a really important concept in C and is one of the hardest to understand, because their is a lot of prerequisite knowledge of what is going on behind the scenes to understand strings. My best advice is to just test strings out a ton in a dummy program to understand what you can and cannot do with them. Reading online articles about them is only advised if you are willing to read a lot, because the first couple of sources you read about the subject I have noticed when researching this are bound to have errors in them, because of how hard this concept is for many people to grasp. In fact many C programmers are completely indifferent to even try to really understand what is going on, which is why so many people have problems with this topic.
Semicolons ;
Semicolons are used to tell the compiler where statements end. All statements require a ; at the end.
Parentheses (((((( ))))))
Parentheses in C are used in three different areas of code:

To change the order of operations of an expression (as you would in mathematics.)
Ex. -3 + 1 * 4 = 1 while (-3 + 1) * 4 = -8

They are also used in function calls and definitions as the area in which to put parameters/arguments.
Ex. int func1(int param1, int param2, int param3); and func1(1,4,99);

Lastly the other place you will see parentheses used is in all forms of branching including if, while, for and case statements. The parentheses here have unique meaning depending on the type of branching you are using, in a while and if statement they are used nearly identical as in expressions except that at least one parentheses is required even if you just have a single variable or constant as the entire expression. In for loops they require a parentheses that must contain three statements, the first one is often used to initialize variables (especially a counting variable that keeps track of how many times the loop has run, the second statement for the condition that must be true for the loop to run and lastly a statement that is often used to increment or decrement or in anyway modify a variable (usually the one initialized in the first statement). It should be noted that this is just the most common use cases for these three expressions and not mandatory, in fact you can leave all three of these statements empty and just have the three required semicolons; and this is equivalent to a while(1) and is a way to initiate a infinite loop.
Braces {{{{{{ }}}}}}
Braces in C are strictly used for scope. Used to group multiple statements together in “blocks” that are also called compound statements. Things created in {} can inherit values from parent braces, but parents cannot use child created values (but can use any changes to variables that were created in their own block or parent block and only modified in child blocks).
Brackets [ ]
Brackets in C serve a distinct function in the code. Brackets are solely used in array notation to denote that the variable is an array. It is also used with an int or an int variable to denote the nth element of an array or its size during declaration. For example to declare and define and array we can do this:
char chArray[6] = {h, e, l, l, o, \0};
The number in the brackets referred to the size of the array, in this case it stored 6 characters. In a following example we could do:
printf(“%c”, array[0]); 
Which would print a ‘h’ on the screen because arrays start at value 0. (Most languages start arrays at value 0 with few exceptions (Lua, COBOL, Fortran, R, MATLAB, and AWK to name of few examples)) (Explained more under arrays)
Format Specifiers
printf Family Format (inside brackets[] indicates optional):
%[flag(s)][field width][. precision][length modifier]conversion-specifier 

scanf Family Format (inside brackets[] indicates optional):
%[flag(s)][field width][length modifier]conversion-specifier 

The field width is the minimum amount of space the conversion will take up, if the number/value passed into the conversion specifier is smaller than the field width specified then spaces will be added to the left of the value until it fills the space designated by the field width. If the 0 flag is specified then 0s will fill the space rather than spaces. Sign characters such as the (+ -) also take up a space.
Flags:
- Left justifies the conversion. If left out the default is that the string is right justified to the field width.
+ Adds a positive '+' sign to the conversion if it is a positive number. Default is no prefix to positive numbers, only a '-' on negative numbers.
# Not really sure tbh, when used with the g/G conversion specifiers it removes trailing 0s after the decimal place.
0 Adds preceding 0s to fill the field-width as needed.
' ' (space)
(only scanf family of functions) Used with 'c', 'n' and '[set]' conversion specifiers that are whitespace sensitive. This tells them to skip preceding whitespace and start at the first non-whitespace character.
* (only scanf family of functions at least as a flag) tells scanf to match the conversion specifier, but do not store it in a variable. In printf it can be a placeholder in both the precision and the field width to which it is used as a placeholder for an argument to be passed after the formatted string with an integer value that will be used in the asterisks place.

%* Depends on if it is used in the printf or the scanf family of functions. In printf it functions as a place holder for an int to be passed in via argument before one of the aforementioned escape sequences. In scanf it essentially means "skip" this in the string and do not store it in a variable. This is useful if you have garbage you don't care about between some information you do care about that you want to store in variables, this lets you skip the garbage and still be able to assign to the variables you want.

Example 1:
scanf("%d %*s %*s %d", &i, &j);
printf("%d, %d.\n", i, j);

Stores two integers that have a string of character between them in two variables i and j. SO if the input was 123 wofjw lnwv 987, the out put onto the screen would be 123, 987.
%% Escape to allow % to be printed
%[A-Z]
Used in scanf functions only, reads up until the character(s) (ABC...Z in this case) are NOT used (inverse of following format specifier), can use escape sequence characters as well. Stores it in a variable (character array). Note that this format specifier does not play well with %n on some implementations and makes that format specifier impossible to use in conjunction with this one. Can use a range of characters like in this case or can use single characters or a list of comma separated characters including non-printing characters that require escape sequences. This format specifier is actually called a scanset in the C Standard.
%[^X] Used in scanf functions only, reads up until the character (X in this case), can use escape sequence characters as well. Stores it in a variable (character array). Note that this format specifier does not play well with %n on some implementations and makes that format specifier impossible to use in conjunction with this one. This format specifier is actually called a scanset in the C Standard.
%n Stores the number of characters preceding it into the variable
%c Character (any ASCII character)(need an array for Unicode characters)
%s String in human readable form.
%p Address that the pointer holds
%a/A Converts a floating point number to its hexadecimal equivalent
%d Signed decimal integer.
%u Unsigned integer value
%e/E Scientific notation
%f Float (same as %lf in printf family functions, but do actually differ in scanf family functions (because scanf needs to know the size of the variable and printf doesn't care) Thus for clarity of intent to other programmers reading your code use %f for floats always and %lf for double, but for backwards compatibility you would use %f (if you have to, g++ rejects %lf) )
%g/G Prints the more concise format between float and scientific notation
%x/X Converts the number passed to it to hexadecimal
%o Octal
%i Integer of any type (auto detects)

Length Modifiers: l, ll, L, z, h, hh, j, t.
These are used before a conversion specifier (see above) to promote and denote how much memory to read or print the value.


Examples:
%lf for doubles (%f and %lf are the same in printf, but differ in scanf family (because it has to know exact memory space))
%ld for long integers
%lld for long long integers
%zu for size_t values
%hu for unsigned short integers

%d vs %i
Inside of printf %d and %i act the exact same, where they differ is in scanf. During normal assignment a variable can store a value using decimal: 15, octal 017, or hexadecimal 0xf and the value will be converted to binary and THEN stored in the variable and thus assigning to any one of those values would print 15 to the screen regardless of if used %d or %i in the printf. In scanf, however, due to the limitation of how the function works, %d reads a number until it hits whitespace or a non-number. Because of this trying to store the value 0xf in scanf using %d will create an error as the number 0 will be stored, but then the 'xf' will be left for the next format specifier which will obviously cause hard to find bugs. %i is much more forgiving and will check to see if you are using an octal and hexadecimal number when reading using scanf and then stores the appropriate number into the variable. Again this is a double edge sword because it will assume if it sees a number like 017 that it is in octal and will store the value of 15, while %d will get rid of the leading 0 and store 17 in the value. Now this depends on what you were expecting as to which one to use in this situation.
Escape Sequences
Escape sequences are used in strings to do one of two things: to print a character that does not have a physical representation, such as the newline character which tells the computer to go down a line before printing any thing that comes after, or to prevent a character that has special meaning from being interpreted as that special meaning. Imagine that you have a string that in itself has a quotation mark, the second quotation mark will make the compiler think that it is the end of the last string rather than think it is a string within a string, so in this case you would have to do \" in order to tell the compiler that you really want the quotation mark printed.

\a Audio or visual bell
\b Backspace (moves the cursor back, but does not delete characters, only removes if replaced with later text)
\n Newline
\r Carriage Return (Puts cursor at start of current line)
\t Tab
\v Vertical Tab
\\ Stops a '\' character from being interpreted as an escape character and instead simply as itself
\' Prevents a ' character from being interpreted as a operator and instead treats it as a character itself
\" Prevents the " character from being interpreted as the ending of the string and simply as itself
\0 Adds the null character (zero in binary), used to set the end of a string
\x \xHEX where "HEX" is a hexadecimal number prints the corresponding ASCII character in printf
\u \uFFFF where "FFFF" is a hexadecimal character that refers to a Unicode code point (similar to \x)
\U \UFFFFFFFF just like \u but for Unicode characters above 4 places
\f Form feed, LEGACY
\e Deletes character to its right, LEGACY
\? Prevents sequence from being interpreted as a trigraph, LEGACY
Phases of Translation -> Steps of Compilation (Implementation dependent)
  1. Character Mapping: Replace characters to their corresponding bytes. (Including Trigraphs and escape squences)
  2. Line-splicing: Remove comments and unnecessary whitespace leaving just the text that needs to be interpreted.
  3. String literals are concatenated as needed
  4. Preprocessor is executed.
    • Sifts through entire program as many times as needed to replace all macros with constant expressions.
  5. Compilation: Convert text to machine code
  6. Linking: Combine source files together in proper order of execution.
  7. Note: The compiler compiles code into object files: "file.o" that can contain debugging information and linker information along with the native code it compiled into.
  8. The compiler may also try to be smart and try to optimize your code and make changes to it as necessary. (This is optional)

Preprocessor Directives
# When used at the beginning of a line, tells the preprocessor that the word following it is a preprocessor directive. This is why all of the following directives have a '#' in front of them. Otherwise the compiler will think that it is part of the standard C code and will be confused.
#include Used to copy the contents of a file verbatim at this location in the source code during preprocessing. The directive is followed by a file path ether relative or absolute in double quotes or angle brackets.
#pragma This directive is implementation/compiler specific/dependent. It can do all sorts of things on different compilers, including but not limited to turning on and off features of a compiler in the source code rather than passing flags to it in the terminal. Can be used to turn off all warnings, to make an identifier illegal to use, to run certain functions on startup or exit and many more.
#define Used to create macros in C which are words that expand out to some other literal. Can also be used to make function-like macros. When not used to make macros with arguments, then it will act as a simple find and replace feature making it easier to change text in multiple places by simply modifying it in one spot.

Example 1:
#define MAXLEN 7
This would create a macro named MAXLEN, and everywhere in the code except in strings that this is found will be converted to 7 before compilation even occurs. This is useful because if we have a number we need to use a lot for a specific reason, making it a constant both provides clarity and makes it easy to swap out for testing and so forth in one place, and change it wherever the constant is used.

Example 2:
#define min(X, Y)  ((X) < (Y) ? (X) : (Y))
This creates a function-like macro that will return the lower of two numbers passed to it.

Note: One of the reasons macros, and macro-like functions are used/or used to be used was because they were done at the preprocessor level rather than during execution and were faster. This is negligible today, but still something to keep in mind. The reason for this, is because there is no time wasted in a function call, as it simply replaces the macro with the function everywhere in the code.
## Often referred to as the merging/combining or concatenation operator is used to well... Concatenate the left and right operators.
Example:
#define tokenpaster(n) printf ("var" #n " = %d", var##n)

int main(void)
{

int var1 = 10;
int var2 = 20;
int var2 = 30;

tokenpaster(2);

return 0;
}
This prints: var2 = 20

#error Often used inside of #if or #ifdef preprocessor directives, this #error directive simply throws an error to standard out while stopping any further preprocessing or compilation of the program. Used similar to assert.
#warning Similar to the #error directive, but does not stop further preprocessing and compilation. Still prints the warning to the output.
#undef Undefines a previously defined macro. Often used after #ifdef guards which check what other macros are defined including those specifying what operating system is being used.
#if Conditional operator that runs at the preprocessor level. Checks the value of a variable or constant and then adds the code between it if result returns true, otherwise the code inside of the #if statement is not compiled into the executable.
#else Used like a normal else keyword, but works with the preprocessor #if instead.
#elif Used like an else if statement in C, but again is used with the preprocessor directives #if and #else instead.
#endif Unlike in if statements in C that either execute just one line of code if no brackets are available or whenever it finds the corresponding '}' bracket, this directive is ALWAYS used with #if, #ifdef, and #ifndef to specify where they end.
#ifdef Checks to see if a macro with the same identifier as the word immediately following this directive is defined and if it is returns true and is used like a normal #if.
#ifndef Same as the #ifdef except that it returns true if the following macro is NOT defined and false if it is.
#line Is followed by two arguments and fills the __LINE__ and __FILE__ macros to those values respectively. This is a very useful macro that can help the compiler correct where it thinks errors and warnings are occurring by setting those values yourself.
Preprocessor Macros
_FILE_ Set by the #line preprocessor directive and can be used to help produce better and more clear warning and error messages by specifying where the location of the error or warning really occurred.
_LINE_ Set by the #line preprocessor directive and can be used to help produce better and more clear warning and error messages by specifying where the location of the error or warning really occurred.
_FILE_and_LINE_ Same as above, but together.
_DATE_ A preprocessor macro that expands to the current system date.
_TIME_ A preprocessor macro that expands to the current system time.
_STDC_ Macro that expands to 1 if the current implementation conforms to the C Standard
_STDC_VERSION_ Macro that expands to the version of the standard that the implementation conforms too
_STDC_HOSTED_ Macro that expands to 1 if the current environment conforms to the standard definition of a hosted environment or 0 if it does not.
_cplusplus A macro that returns true if the following compilation unit was compiled by a C++ compiler rather than a C compiler.
_OBJC_ Same as _cpluplus, but for Objective C.
Header and source files
In C it is common to have code divided into many different files. This is done because otherwise code might get too big and complex. Having it all in one file would be really messy. Thankfully C allows programs to be made up of multiple different files that will be combined together during compilation and linking. There are typically two types of files used in C source files, those that end in .c and those that end in .h. Those that end in .c are called c files or source files in every day language while those ending .h are called header files. While the extensions and the names are arbitrary and just convention (A.K.A you can end it in any extension you want, but please don't) it is common for .c files to contain “real” code that has definitions and “code” while .h header files usually just contain function prototypes and declarations to functions, variables and other entities. It is often that one includes one file in another file using the preprocessor #include directive. In most programs you will see #include <stdio.h>, this is telling the preprocessor to go find the stdio.h file and <> specifically tells the preprocessor to look in the $PATH environment variable first. In linux you can find <stdio.h> in the /usr/include directory. If #include “myheader.h” is used instead this is not a standard header and thus will not be found with the rest of the standard library headers unless put their intentionally. The “” are specifying the path to the file relative to the file it is included in. Both <> and “” can have paths given to them instead, but the shorthand is more convenient, so this is usually not necessary. Every now and then you will find a #include directive that has <curses/ncurses.h> or “directory/subdirectory/subdirectory2/file.h”, both of these show how one would let the compiler know exactly how to get to the header file via relative paths. Header files do NOT contain definitions usually and are just used as a way to communicate between .c source files. In fact when you #include <stdio.h> in your source file it finds the header file of that name, which actually contains definitions for another C file that contains all the definitions of that stuff. Usually this is abstracted from us and there is no file that directly has all of these definitions, because the file that would contain said definitions has already been compiled into an object file and is linked into our program. The functions and other tools in stdio.h and other header files that do not need a -l flag at compile time are because a file on the computer (such as libc.a or libc.so) is an already compiled version of those file and is automatically linked during compilation. Other files like math.h and ncurses.h need to be linked to the program you are writing, because the header alone is not enough. Your file says you want to use this tool, the header you include tells the compiler just enough information about what that tool requires from the user (prototypes and such) so that the compiler can tell if you are at least using it correctly. Then in one of the later steps of compilation the file that has all of the real code for those tools is linked and essentially copy and pasted into your program (yes this makes your program bigger, but is essential) to make one bigger program with all of the information needed to actually have the program run. Keep in mind that a lot of this information is implementation defined and mostly just how the GCC compiler works and handles this stuff.
Source code/source files
Source code/source file is the file and code that the programmer actually writes. This is the file where you type all of your code and comments that when edited and compiled changes how your program operates.
List of some C compiler options
  • GCC (GNU Compiler Collection)
  • Tiny C
  • Clang
  • Boreland
  • PCC
  • MSVC
  • IBM XL
  • Lattice C
  • Aztec C

Build Systems
A meta tool that facilitates the building and compilation/deployment of a program. Put simply, it is a tool that you can use to produce a compiled or a file up to any step in the compiling process (see compiling process to learn more) from a given source file. It takes a source file, looks to see if it or any of its specified dependencies have been modified and if they have it compiles any files that need to be while leaving all of the others alone. It is often used to save key presses and subsequently errors one can make when trying to compile a program. In regards to C you can have it complete any or all of the compilation steps and run gcc for you if any of the files you specify in its Makefile have been modified. This is easier to understand with an example. Imagine you have a program that contains three source files labeled: main.c, helper.c, and more_help.c. Now imagine that they have corresponding headers to them. Now lets pretend that all of the files are independent except that helper.c is dependent on main.c. You can set up in your make file that if just more_help.c changes and none of the other files change your compiler command only tries to compile it instead of wasting time also compiling the other files even though they have not been modified since the last compilation. With it being so inefficient the Makefile which is the name of the file that the most common build systems Cmake and Gnu Make use. Now if you modify main.c typing just the word “make” into the terminal and pressing enter would compile both the main.c and the helper.c, but not waste time compiling more_help.c. Make files make it easier for us as programmers by making us only have to type make into the terminal after setting up the dependencies one time in the make file. There are a bunch of other build systems out there other than Gnu Make and CMake such as: Bazel, Meson, Ninja and so many more.
Data in C
Types
Types are the kind of value a variable will hold or expression will come out to be. The compiler needs to know a variable’s type in order to know how it can be used, and so that it knows how much memory to set aside. There are two types in C, primitive and non-primitive/derived/advanced types. Primitive types include: int, char, float, double, long, short, long long and *(pointer). Non-primitive types include arrays, structs, enums, and unions. The compiler also needs to know where to set aside the storage which will be talked about later when discussing: heap vs stack vs static. EVERYTHING in C is a number. The only thing that differs is their sizes, how the bits are used within those sizes and the assumptions that can be made by the compiler (and us) about how they will be used. Keywords that specify the size of a variable are listed immediately below in the relative size in comparison to the others.
char <= short <= int <= * (pointer) <= long <= long long
float <= double <= long double

Note: *pointers might be assigned and stored as numbers, but its implementation defined if they can be used in expressions

Other keywords including the aforementioned float and double as well as signed and unsigned specify how the bits in those respectable sizes are used. For example: a signed int will use the most significant bit (learn about endianess for more information about that) to determine if the number is negative or positive. Floats and doubles will always have a signed bit and have specific bits for the value of its exponent (a power of two) and the mantissa. (further reading may be required). Pointers are the one caveat to the simpleness of all the basic types. Pointers are different than the others in the way the compiler handles them. They essentially just hold numbers (often of size int or long), but they can’t usually be used in expressions and assignment like others. For example, if I create a pointer to an int like: int* foo = 3; and later in the code I increment it using the increment operator like so: foo++; The value that printf would print out would now be 7 on my machine. This is because on my machine an int (which was the value I said the pointer was pointing to) takes up four bytes on my machine, meaning that when I incremented it by one using a++ the compiler thought I meant to add up to where the next int could be stored and thus added 4 instead of what I would normally expect the ++ operator to do which is just to add one. This sorta makes sense because the whole point (get it?) of pointers is to help with working with memory whether in an array or in the heap. Thus when I add one to a pointer to an array of ints, I expect it to go to the next memory location that contains a separate value. If it simply added just one, I would still be in the same block of memory I was just in last time and would have to manually remember how much space an int and every type takes up with the computer I am compiling for is. Thus the only operations that can be done with expressions are addition, subtraction, increment, decrement and assignment. The void type is used in two different ways. When used during a function declaration and function call it means “nothing”. If it is used with an asterisk (*) after it, then it is being used to tell the compiler, that I am creating a pointer, but I don’t yet know what I am pointing it to. Void pointers are temporary and must be cast to a specific type to be used later. Think of it meaning “nothing yet” in this case. More advanced types including structs and arrays have the ability to store multiple variables at the same time. Unions have the ability to store one value at a time, but can be one of multiple types. More on all of them later.
Expressions and Statements
Think of it like an English sentence. To form sentences you need a subject and a predicate. An expression is when you just have a predicate or just have a subject, but a statement is when you have both. x by itself is an expression, 3 + 6 is an expression, (6 + 3) * 2 is also an expression, but int x; and then later x = 3 + 6; and functionName(parameter 1); are statements. As you may notice from the examples a statement is any line that ends with a semi colon ‘;’
Declarations
Declarations are the first appearance of an entity (variable, struct, enum, union, function, array, etc.) that tells the compiler the name and the type that it will use. This lets the compiler infer the possible ways it can be used, approximately how much storage it will need, and more specifically where in memory to store it. Keep in mind all that declarations do is set aside space and memory and label it, initialization/assignment is separate although it occurs often/always at the same time. An example of a declaration is:

double var; //This will set aside a block of memory large enough to store a double and label it "var".


Another example of a declaration is a function prototype like:

double div2Nums(int param1, int param2); // this tells the compiler a lot. Firstly it implies that there is a function defined later that returns a double when two integer values are passed into it, and also the name to call it by. 


Note: Variables are always initialized to a value by default. This depends on a lot of factors on whether it is zero or not by default and will be explained in greater detail later. Just keep in mind that depending on those factors you may get unexpected results when trying to use a variable that is declared but never assigned by the programmer.
Definitions
Definitions on the other hand are the completion of declarations. Again think of declarations as the skeleton and the definition fills out the remaining holes and completes the thought. Definitions are when we assign variables and is closely tied to the idea of expressions, initialization and statements. You do not have a fully working piece of code until a variable, struct, function or so on has both a declaration and definition. An example of a definition would be:

x = 3; 
or
double div2nums(int param1, int param2) { double sum = param1 / param2; return sum; } // this function is now complete and can be called and used in your code. Once you add an already declared element with a definition you have completed the requirement to use it. 


int x; //could store any value (undefined/garbage) (unless static or extern)
int x = 5; //stores a specific value of 5.

Initialization and Assignment
Assignment is storing a value inside of a memory location. This is usually done with the '=' sign which is referred to as the assignment operator which "assigns" the rvalue (value on the right) to the area in memory referenced in the lvalue (left of equal sign). Initialization and assignment in C is very closely tied in to the idea of expressions, statements, declarations and definitions. Initialization is often used when talking about two specific situations in C. Both, however, occur when a variable is being declared, initialization is the default value the variable will have which depends on if the variable is declared with the assignment variable or not and the storage class of the variable. Auto and register storage class variables have undefined/garbage value by default. This means that if you create a variable with either auto or register storage classes (local/block scope variables) and then try to use the variable without first assigning it to something, the result could vary each time you run the program making it hard to debug and predict. Static variables and externally linked global variables on the other hand DO have known values by default as all of these variables are initialized to 0 by default if not assigned by the programmer. Assignment is the act of using the ‘=’ sign in the code to change the value of a variable. If the assignment occurs on the same line as the variable is declared, then this is often considered initializing to that value, but any other time you use the equal sign later in the program it is not considered initialization.

Note: assignment of pointers using the * is unlike all other types. When you declare multiple variables with an asterisk, only the first variable is a pointer and the rest are of the type before the asterisk.

Example:

int *a,b,c;
int *d,*e,*f;

printf("sizeof() returns: %zu, %zu, %zu\n", sizeof(a), sizeof(b), sizeof(c));

printf("sizeof() returns: %zu, %zu, %zu\n", sizeof(d), sizeof(e), sizeof(f));
Prints:
sizeof returns: 8, 4, 4
sizeof returns: 8, 8, 8
int* a,b,c;
is equivalent to:
int *a;
int b;
int c;
Where only the variable 'a' is created as a pointer to an int and the following variables are just type int.

Designated Initialization
Designated Initialization is a topic you might find or hear about when learning about C programming. Designation Initialization is the specific type of designation used for arrays and structs that you can use during declarations.

As you can read in those sections of the sight all about arrays and structs, one thing that was missing was partial definitions during declaration. What I mean by that is assume I make an array of 10 integers and also assume I make a struct that has three members named var1, var2, and var3 respectively. In the examples I used during my sections isolated about arrays and structs I showed how you can use (variable declaration) = { value, value} in order to assign things to both arrays and structs at the time you declare them, what I did not mention was that you do not have to assign everything during declaration if you do not want to and in fact you do not have to assign them in order if you have some reason not to. (Note that assigning them out of order is a c only operation and breaks compatibility with C++ if that matters to you.).
 int array[10] = {[9] = 0, [0] = 62}; //this assigns only the 9th (last) element and the 0th (first) element in the array to a value and everything else will be unassigned (unless declared statically or globally).
struct myPerson {
    char* name;
    short age;
    short weight;
};
    struct myPerson clay = {.weight = 205, .age = 23}; //only members age and weight are assigned and member name is uninitialized ( or is initialized to 0/NULL if declared in static memory)

    clay.name = "Clay";

    printf("%s, %d, %d\n", clay.name, clay.age, clay.weight);
Prints: "Clay, 23, 205" to the screen.
Arrays
Arrays are special type of data structure that can be used to store several values of the same type in a continuous block of memory. This allows the programmer to make space in memory to hold several values in one line of code without having to make and subsequently remember unique names for variables to store them all in. Usually this is done when you have a lot of related data that all must have the same type. For example, if you wanted to calculate your GPA in school, you COULD create a variable for every class you have ever taken at the school and then store the letter grade or the GPA weight-adjusted scores in each one of them. After doing that you would have to have (depending on the amount of classes you have taken) an excruciatingly long list of variables you would have to remember as well as make sure you spell them all correctly when you start calculating your average by adding them all together like: english101 + english201 + math165 + history171 + … and then divide them by the number of classes you had taken. This is tedious, error-prone, and makes your code really hard to debug and understand. Instead what you could do is store all the information in a single data structure (an array) and make life so much easier. For this example we are going to use the GPA scale both colleges I attended used for GPA calculations which is as follows:

A/A+ = 4.0, A- = 3.7, B+ = 3.3, B = 3.0, B- = 2.7, C+ = 2.3, C = 2.0, C- = 1.7, D+ = 1.3, D = 1.0, D- = .7, F = 0

    #define CLASSES_TAKEN = 8;
double my_gpa[CLASSES_TAKEN] = {3.3, 2.3, 1.7, 4.0, 3.7, 3.7, 3.7, 2.7, 3.0}; //error exceeds array limit double my_gpa[CLASSES_TAKEN] = {3.3, 2.3, 1.7, 4.0, 3.7, 3.7, 3.7, 2.7}; // Much better! or double my_gpa[8]; my_gpa[0] = 3.3; my_gpa[7] = 2.7; my_gpa[2] = 1.7; my_gpa[1] = 2.3; my_gpa[5] = 3.7; my_gpa[6] = 3.7; my_gpa[4] = 3.7; my_gpa[8] = 3.0; // ERROR, exceeds array boundaries
Array was assigned out of order and my_gpa[3] was excluded just to show that you can. Keep in mind that if an array is defined in local/auto scope/storage class than it is implementation defined, but common for the values to be initialized in those elements to be garbage/undefined. And thus my_gpa[3] could hold any value.

Arrays have their own syntax including [] when declared and {} when defined that directs the values passed into it into a linear block of memory where a group of similar type variables are stored. This makes it easy to just increment to see the next value in the array. Arrays are a convenience tool that allows us to access related values much easier than if we stored them all in unique variable names. An array is very useful and essential for strings in C. Strings are really just arrays of chars in C with an ending NULL ‘\0’ character at the end of them.

Has its own pointer notation. The name of the array is just a pointer to the first value in the array. This is why arrays start at 0, because the first value IS at the location of the pointer or I could say it is 0 spots away from the pointer to the array, while the second element is 1 spot away from the array pointer and the 99th element is 98 spots away from the beginning of the array. Think of the index as the distance from the begging of the array.
ARRAYS ARE NOT POINTERS! (at least, not just pointers) Regardless of what you hear from anybody else. They are often used and act like pointers, but this is because in all, but a few scenarios they “DECAY” into pointers. This occurs when used with operators such as: sizeof(), the unary &, or the _Alignof operators. The reason for this “decay” is that when one declares an array, you have to specify the size of it (except for dynamically allocated arrays). Due to the fact that one must specify the number of elements at compile time the compiler knows both the pointer/location of the array, and also the size of it. So really it is an array + information the size it takes up. When you pass an array to a function, or pass one of its elements, you lose the size information. That is called “decaying”, as now the compiler only knows that it is a pointer to a location, but not what its size is. From that point forward it really is nothing more than a pointer to a spot in memory followed but sequential memory locations to store a elements of similar types.

Arrays store data in continuous blocks of memory, it is because of this that indexing and adding works to find the next element in the array. Because in memory their is a block set aside with just enough room to store the type of value of the array, and then immediately right after that block of memory it stores the next block containing the next element in the array.
Arrays can be multi-dimensional meaning that they can store and group more data in more complex yet rigid ways and still quite easily index through each element in the array. Multi-dimensional arrays are still stored in continuous blocks of memory, but the beginning of the second "row" in an array starts immediately after the last element in the first "row". This is so much easier to understand with examples.
I am going to show how to initialize and use two and three dimensional arrays so that you get a feel of how this all works and then you should hopefully have the knowledge of how to apply this to even more dimensions and thus could create 15 dimension arrays (which is pretty unrealistic for practical everyday programming, but nonetheless possible).

For this example I am going to create a two dimensional array and three dimensional array. The two dimensional array is going to store a list of words because that is a very common use case for strings. The 3D array is going to store an array of character pointers to strings and be journal calendar of sorts that will keep track of the day, months and year that will be used as a historical notes about "today in history" and record events that happened on each day, of each month, of each year (which are the subsequent three dimensions).

//12 months in one year, up to 31 days in a month (a few months will just not use the 31st index.)
const int MAXWORDS = 5;
const in WORDLEN = 20;
char* [MAXWORDS][WORDLEN];

for (int i = 0; i < MAXWORDS; ++i) {
	for (int j = 0; j < WORDLEN; ++j) {
		one_year_journal[i][j] = NULL;
	}
}

Its good practice (at times) to set all of the values to 0 (NULL) so that if you do not end up assigning something to a specific index you still know what to look for in that case and the value is not garbage. It should also be noted now that arrays and for loops go hand-in-hand if you don't know arrays, you don't really know for-loops and if you don't really know for-loops you do not really know arrays, or at least are not getting the most out of either. Arrays are the perfect use case for for-loops because unless dynamically allocated you always know the exact length of the array which means you just have to iterate a set amount of times to get to each index of the array. I should also emphasize the fact that I created two constants before I created the array and then used them both during the declaration of the array and in the subsequent for-loops. This is for two huge reasons, one is so that if I ever change my mind and change the maximum length a word can be, or if I decide to allow more or not allow as many words in my array I only have to change those two variables in my code and nowhere else and my code will work just fine. The other reason is one that you may here spoken of every now and then in programming which is to prevent "MAGIC NUMBERS". Magic numbers are when you use numbers in your code which have meaning (in this case I could have put the number 12 and 31 in the for-loop and in the array when I initialized it and the code would work the same, but then when someone else who has never seen this code before tried reading it the would have to do a ton of digging and mental gymnastics at time to figure out what is so special about 12 and 31. By creating a constant variable with great names such as MAXWORDS and WORDLEN it is pretty obvious what their purpose is.

You can initialize two dimensional arrays like other arrays like so:

char words[5][20] = {
	{'C','L','A','Y','\0'},
	{'G','e','o','r','g','e','\n',0},
	"Apple",
	{'H','i','\0'},
	{' ',0}
};

printf("%s", words[1][0]);
Prints George to the screen.

printf("%s", words[1]);

Also prints George to the screen.

printf("%s", words[0][2]);

Prints AY to the screen.

And like:

char words[5][20];

words[0][0] = 'C';
words[0][1] = 'L';
words[0][2] = 'A';
words[0][3] = 'Y';
words[0][4] = '\0';
words[2][0] = 'A';
words[2][1] = 'p';
words[2][2] = 'p';
words[2][3] = 'l';
words[2][4] = 'e';
words[2][5] = 0;

I skipped some because I am hoping you get the point. Keep in mind the fact that all of the memory in arrays are continuous as that means that after the end of the first index [0][19] the next element [0][20] is the exact same position as [1][0] and thus would return the same value. Also the value [2][41] would be the same as [4][2].

char *_09_03_1998 = "Today I was born.";
char *_07_30_1965 = "LBJ signed Medicare into law.";
char *_09_11_2001 = "Terrorists attack the World Trade Center in New York.";
char* events_since_ad[4000][12][366];

events_since_ad[1998][9][3] = _09_03_1998;
events_since_ad[1965][7][30] = _07_30_1965;
events_since_ad[2001][9][11] = _09_11_2001;

printf("%s\n",events_since_ad[1998][9][3]);
Prints "Today I was born." onto the console.
Pointers
An advanced variable “type” that stores the memory address of another variable. & is used when defining a pointer variable to pass the address value of the RValue (think of & as the “address of” operator. * is used to “dereference the pointer” to get the actual value of what is stored at the address. If you dereference a pointer to a pointer you will get the address of what the dereferenced pointer is pointing two and if you want the vale of what is at that point you need to dereference it multiple times by stacking *’s as needed. For example if I have a: pointer to a pointer to a pointer to a pointer to an int and I want its value I would have to ****pointerName to get the value of the integer.

Why do we want/need them?

To get around “pass by value”:
Everything in C is pass by value meaning that when variables are shared with functions they are copied and then if the variables are modified in the function, it does not modify the original. But what if you want to? Then you can pass in a reference to a variable and then any changes made to it from within the function is also made to the variable outside of the function (this is a part of what is called side effects)

To return more than one thing from a function:
As mentioned above, the values passed by pointer into functions can be modified. Most functions end with a return statement that exits the function while returning a value of something. Due to the fact that it automatically kicks the program out of the function, what if we wanted to return more than one thing? With pass by value only you can not do this. A way around this could be to call the function several times, or to pass in pointers and modify them before the return statement to change as many variables as you please.

To get around scope:
Similar to the aforementioned reasons, but it is important to understand that variables created inside of all functions have local scoping to just those functions. This means that variables created in one function can not normally be used used inside of any other function. This is why variables can have the same name as previous variables of different scope. One can have a variable X in the main function and also have a different variable called X in any other function. Functions are walled gardens that when they are exited there variables are removed from memory. So if you want to pass values between variables you can chain several return statements together or you can have them pass values to pointer variables so they can all access the same data and modify them collaboratively.

To save memory space and cpu cycles:
More advanced and user created types can be much larger than those of simple ints and doubles. Arrays for example can store large quantities of ints taking up significant amounts of memory at times. It would be really inefficient to make a copy of a really large type or array slowly copying all the values into a new variable just for the lifetime of the function. Instead pointers are a single value type that is roughly the size of a long integer in size and can be used as a reference to a much larger piece of data. When wanting to read or manipulate the data in the function you simply de-reference the pointer using the *variable_name which will go to the location in memory and return just the result of that place in memory. You can also assign a new value to that single point in memory with no prior copying needing have been done.

In the example below I am storing all of my favorite numbers in a integer array so that I can refer to them all by a single name so I do not have to create and memorize a unique name for every value. Arrays as we say DECAY into pointers meaning that I can refer to any of the values in the array if I know its location in memory (pointer). Arrays and pointers are intrinsically related as arrays are sequential blocks of memory and pointers are just ways to refer to places in memory and can be used with simple math to find other locations in memory (especially those adjacent to themselves as is the case in arrays).
int favNumbers[10] { 3, 7, 20, 21, 22, 24, 80, 88} 
Now when I want to access them all I have to remember is the array name favNumbers and I can quickly access any of them as I wish. Strings are similar as they are just arrays of chars.

Functions
Functions are blocks of code that can be called by an identifier to run multiple times within a program. This means that the code written in a function only needs to be written once, but can be used several times and at times with new values thrown into them so they can produce new output. While neither being essential functions often have parameters that can be passed into them (arguments when passed) that are copied into the function and manipulated. The function does something, and if necessary returns a value. If more than one thing needs to be returned from a function then pointers must be used. Parameters are the values expected by the function and arguments are the values actually passed in. The terms are sometimes used interchangeably, but technically differ. Functions either need to be declared before they can be used in one’s code. This can either be done by fully defining the function above where it is used or the more prevalent way which is done by prototyping the function above main or in a header file and having it actually defined somewhere later or elsewhere in the source code. Variables declared in a function have local scope by default and will be overwritten in the stack when the program jumps out of the function. Arguments are always passed by value into the function. So when parameters are modified in a function, that is the only variable that is modified at that time.

RETURNTYPE NAME(TYPE PARAMETER,TYPE ANOTHER PARAM);    // Function Prototype (usually above main or in header files.

RETURNTYPE NAME(TYPE PARAMETER, TYPE ANOTHER PARAM)	// function definition (usually below main function)
{
	code;
	more code;
	return EXPRESSION;	// this would be omitted if return type is void
}

name(argument 1, argument);	// function call, with two arguments passed into it


Purpose of functions:
  Reduce redundant code:
This makes the code much easier to read as there is less boilerplate and repeated lines over and over because you only have to write stuff once it is less prone to simple mistyping errors.

  Modularity:
Functions can sometimes be used in a variety of different contexts making them versatile little tools one can use anywhere in their code rather than just in one spot and at one time.

Main Function and Command Line Arguments
The main function is unique in comparison to all other functions in that it is not called from any other function, as it itself is the starting point for your code. All code above the main function is in "static/global" space and no real code is put there, just declarations and preprocessor macros and functions which are not executed until they are called either from the main function or another function in which itself was called from the main function as everything starts there. The main function is where the global _start in assembly tells the computer where to start executing the code. Like other functions things declared inside of this function have the scope and lifetime of this function unless the static keyword is used or pointers to them are used. Unlike other functions- there is four (really just two) options to use as parameters when defining this function. This is to leave them blank: main(), which is the same as the second option which is more declarative in purpose which is main(void). The third and fourth ways are also equivalent to each other, and only differ in how they appear and are read by the reader and are solely subjective as to which you prefer. They are: main(int argc, char* argv[]) and main(int argc, char** argv). Where the names argc and argv are conventional names that everyone uses, but can technically be any name you want. The types however are strict and must be an int and an array of strings. These are the only two parameters that can be taken from main and they are the arguments that are passed into the program from the command prompt when running it. argc is an argument which simply tells you how many arguments were passed to the main function. argv on the other hand is an array of strings that actually holds all of the arguments passed to the program. Thus main is a variadic function that can take several arguments, but it is your job as the programmer to determine what has been passed and how to use them on each call of the program.
#include <stdio.h>


int main(int random, char *justForFun[])
//int main(int argc, char *argv[])
//int main(int argc, char **argv) //both are equivilent.
{
	printf("Number of arguments: %d\n", random);	
	//printf("Number of arguments: %d\n", argc);	
	
	for (int i = 0; i < random; ++i) {
	//for (int i = 0; i < argc; ++i) {
		printf("Arugument #%d: %s\n", i, justForFun[i]);
		//printf("Arugument #%d: %s\n", i, argv[i]);
	}

	return 0;
}
./a.out one two three
The previous call of the executable with arguments produces:
Number of arguments: 4
Argument #0: ./a.out
Argument #1: one
Argument #2: two
Argument #3: three

Type Casting
Type casting in C is a way to temporarily switch the way we interpret what value a variable or expression can be. It is simply a way to temporarily change the type the program thinks a variable can hold. This can be done in two ways, implicitly or explicitly. When expressions contain multiple different type variables or numbers there is a default implicit conversion being done which will be explained a little further down. If we want to add or modify this default behavior for one reason or another we can do so by type casting explicitly. This is best explained with the following examples showing implicit and then after the next paragraph explicit type casting:

int x = 2;
int y = 3;
int answer = 3 / 2;
double answer2 = 3 / 2;
printf(“%d”, answer); //prints “1” to the console.
printf(“%.2lf”, answer2); //prints “1.00” to the screen 

This has to do with the fact that we were doing integer arithmetic which can only produce WHOLE NUMBERS. This is because ints unlike doubles do not contain information about where to put the decimal and the like. Even in the second scenario when we use double to store the value it still loses the .50 it should have and prints .00 in the decimal positions. This is because it lost the .50 part before it was even assigned to the variable. In order to get the right answer we have to do one of two things. We either have to change the variable types to be doubles or floats or we can type cast them. The reason why you would want to type cast them instead of just change their types is because maybe you use these variables other places in your code that is explicitly reliant on the fact that these are not doubles and floats and will only contain whole numbers, so if you only need it to act like a double or float for one specific or very few operations, then you can type cast them.

int x = 2;
int y = 3;
int answer = (double) 3 / (double) 2;
double answer2 = (double) 3 / (double) 2;
double answer3 = 3 / (double) 2;
double answer4 = (double) 3 / 2;
printf(“%d”, answer); //prints “1” to the console.
printf(“%.2lf”, answer2); //prints “1.50” to the screen
printf(“%.2lf”, answer3); //prints “1.50” to the screen
printf(“%.2lf”, answer4); //prints “1.50” to the screen 

Notice two things. In the “answer” variable printf statement, the answer was still just 1 and not the right mathematical answer of 1.5. This is because even though we type cast them during their expression, they still lose the information about the stuff after the decimal place because the variable they are being stored into is of type integer. Secondly notice how it does not matter if I cast both or just the left or just the right of the expression to double as the answer will remain the same. This is because of a concept called “promoting” in C. This is when any variable with less information is used in a expression with another variable with greater information it is promoted to the greatest information type in the expression. There is an order to which types get promoted which from least information (most often to be promoted) to most information (least likely to be promoted) is:

int -> unsigned int -> long -> unsigned long -> long long -> unsigned long long -> float -> double -> long double  

🔨 lvalues and rvalues
lvalues and rvalues are subsequently short for left values and right values in respect to the '=' assignment operator. There are specific rules of what can be on the left and what can be on the right of an =/assignment operator. This is due to the fact that I must repeat that the = sign DOES NOT mean equal in C, it means “is assigned to”. Unlike in math where every algebraic equation can swapped ((x = 5, and 5 = x) are both legal in math, but this is not the case in c. The 5 must be on the right hand side a.k.a. an rvalue while the x can actually be on either side. lvalues and rvalues are not always and have not always been seen as left and right values, but lvalue is sometimes referred to as locator values, which means that it refers to objects in which you can get the address of, while rvlaues is a term that is deprecated and for constants/expressions that you cannot get the address of. "Strings" are lvalues and this topic needs meed to look into it more to explain why. lvalues are things that have a location in memory and rvalues are things that do not.
char (*str)[15] = &"This is valid!";

x = 5 + 1 // valid

5 = x + 1 // invalid

y = x + 5 + 1 // valid

x = x + 5 + 1 // valid

The last one will seem really weird to non programmers, and pure mathematicians, because how can a value be equal to itself plus or minus another number? AGAIN, ‘=’ does NOT mean equal. It means figure out the value of everything to the right of it, and then store it in the object to the left of the ‘=’ sign. The reason that five can not be a lvalue is because it is a literal which means that it just means 5 and nothing else. While x in the example does not mean the letter ‘x’, it means the name of the location in memory that we labeled ‘x’.
Unions
A way to store a single value in different types, but not simultaneously. Looks, acts and is defined like a struct but only one member can be filled at any one time. Takes up less space than structs because only one member is used at any given time. Kind of like an if,else for a struct member where one or the other can be stored but not both simultaneously. Act very similar to structs in how they are called as well.
union (optional structure tag) {
        member declaration;
        member declaration;
        ...
        member declaration;
} (0+ variables of this union type);

union human {
        int age;
        int heightFt;
        int heightInch;
        char* name;
        char gender;
        double weight;
}clay;
Enums
#define and const alternative that has scope and type safety. However, they can only be of type int. Enums are similar to structs and unions in that they can be used to create types and can be declared in a manner that is like a hybrid to that of array initialization using bracket and commas and structs and unions.

enum tag {CONSTANT, ANOTHER CONSTANT, etc.}variableOfThisType (if any);

enum days {Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday} test1;

enum days test2 = Tuesday;

test1 = Thursday;

enum days test3 = 65;  //valid, not an error

printf("%d\t%d\t%d\t%d\n", test1, test2, test3, Friday); //prints 3 1   65  4

This creates seven constants, namely the days of the week that are now associated with the values 0-6 respectively. They have the lifetime and scope they are created in, meaning no other variables can use these identifiers.
int Monday = 7; //ERROR cannot have two variable with the same identifier in the same scope

"Although variables of enum types may be declared, compilers need not check that what you store in such a variable is a valid value for the enumeration." -Brian Kernighan (author of the most popular book on C) or Dennis Ritchie (creator of the language and co-author)

This means that although you can create a type of a declared enumeration, while it may seem plausible that it then would be restricted to only hold values contained from its declaration, this is not the case as seen above with the 65 being stored in one. This is not to say that creating variables of enum types is completely useless, as it still conveys meaning to other programmers and people reading the code that it should probably only hold those values, but this would be terribly confusing if you then went on to use it to store an integer other than those defined. Really the only use case for the enums then is to create constants with scope and lifetime.

Function Pointers
Function pointers are nearly identical to all other pointers except that you have to give them more information as well as give them a slightly modified notation, due to the thing you are pointing to being more complex. Usually when you declare a pointer to a variable, you only need to specify its type and then add an asterisk and give it a name. Now you have to do that, plug wrap the name you give it in parentheses and then also include information about any parameters that the function requires. Below is an examples:

#include <stdio.h>
void print_sum(int (*name)(int), int b)
{
    int a = name(b);
    printf("The sum is: %d\n", a + b);
}

int subtract_one(int a)
{
    return a - 1;
}

int main(void)
{
    print_sum(&subtract_one, 7);
    return 0;
}

int *ptr = &a;
1^   ^2    ^3

Where ‘a’ is the name of an already existing variable of type int. 1 is the type that the pointer will point to (int in this case), 2 is the name of the pointer variable (ptr in this case), and 3 is the operator to return the address of its right operand (in this case the address of variable 'a' will be assigned to ptr).

int (*ptr)(double, char) = &a; 

Where ‘a’ is the name of an already existing function that wants a double and a char as parameters and returns an int. "int" is the return type of the function ptr is the name of the pointer variable. Double and char are the parameters of the function in which we are creating a pointer to, and then we are assigning it all to a function in which we assume its name is 'a'.


Function pointers and arrays of function pointers are often used in a callback mechanism, which is when in a loop (usually a while loop specifically) the user is waiting for an event to happen. When an event occurs, you have a way of handling what the event was, and depending on what event it was call a specific function to do a specific thing. Maybe you want a specific function for when you press the 'w' key, and another if you press the 'a' key such as in a video game when users often use those keys to move around. Maybe in this example, you call the function to move the character forward a space given the 'w' key, and have the character move left if the 'a' key is pressed.
Structs
A way to create more complex types by storing multiple values that you want grouped together at the same time, but may not all have the same type as required by arrays. Has its own pointer notation. Like functions, structs must be known to the compiler/declared before they can be used and must occur beforehand so they can be used in a function. (kinda like a prototype’s purpose) This is why struct definitions are usually declared outside of the main functions so that they have global scope. Then new structs of those types are declared in the main function usually and can be passed into functions like other values. Structs have their own namespace which is to prevent it from cluttering with the common namespace of regular variables and functions. So the struct type name does not share namespace, but the name of a new struct of that type is in regular namespace. Nameless structs like nameless unions are special types of structs that do not require an identifier when declared/defined are usually found inside of of structs and unions so that their members can be accessed as if they were from the parents struct/union members. Anonymous structs can also declare a few variables of this type during declaration that will then be the only ever variables with this type since their is no way to define a type from a nameless struct or union after its initial declaration.

Struct type declaration and definition (and declaration of clay which is a variable of type human):

struct human {
    	int age;
    	int heightFt;
    	int heightInch;
    	char* name;
    	char gender;
    	double weight;
}clay;
Declaration for struct of type human:
struct human person1;
Definition for struct human person1 (all the values are assigned below, but you can define whichever you want and in whatever order you want if you so choose):

person1.age = 35;
person1.heightFt = 5;
person1.heightInch = 10;
person1.name = “Joseph”;
person1.gender = ‘M’;
person1.weight = 215.32;

They can also be defined in a similar fashion to arrays at declaration with bracket {} notation.

As you can tell in order to get the “members” of the structs (the values of the variables held in the struct) you use the ‘.’ operator. If you passed this to a function by pointer then one would simply have to replace the ‘.’ operator with the ‘->’ arrow operator instead. Other than that they are like any other variables.
struct (optional structure tag) {
   	member declaration;
   	member declaration;
   	...
   	member declaration;
} (0+ variables of this struct type);

If the optional structure tag is left out then the structure is a nameless/anonymous structure, which must be defined within another struct or union to have any use.
Bit Fields
Bit fields in C are special attributes that can be given to primative type variables held within a struct or a union specifically. When declaring a variable with a primative type you can add a colon : and a number to specify how many bits to use for the variable. This allows us to create multiple variables and then if the total amount of bits used with all of our bitfields is less than the total allocated for a single variable then only that much space will be taken up. As soon as we use more bits than is required for a single variable of that type the amount of memory used in the struct or union grows to the size of two variables until we use as many bits two variables takes up. After filling more bits than two variables takes up the compiler allocates room for three variables of that size. Keep in mind that the total number of bits used cannot exceed the amount of total memory of the size of the type of variables multiplied by the total number of variables in the struct. Essentially bit fields are a way for us to save space when using either structs or unions, this is helpful to us if we have a bunch of values that will only contain very small numbers then we can combine them to be the size of a single variable. This I think will all make more sense with the example down below.

To create a bit field, when declaring a variable of a primative type within a struct or a union, add a colon after its identifier and give it a number which will correspond to the number of bits to use for this variable.

#include <stdio.h>

#pragma pack(1) //if included will make the structure fit in as tightly as possible, otherwise padding will be added so that everything fits nicely in powers of two in memory, not that adding this preprocessor directive can lead to slower and larger executables because it turns off compiler optimizations
struct random {
    	unsigned int random: 18; //111111111111111111 0->262143
    	unsigned int random2: 9; // 111111111 0->511
    	int random3: 4; //1111 -8->7
    	unsigned int random4: 6;  //11111 0->63
}random;

int main(void)
{
    	random.random = 262143;
    	random.random2 = 511;
    	random.random3 = 7;
    	random.random4 = 63;
    	printf("Sizeof() random struct: %zu\n", sizeof(random));
    	printf("random.random is: %d, random.random2 is: %d, random.random3 is: %d\n", random.random, random.random2, random.random3);

    	return 0;
}

As it stands what is printed on the screen is this:
Sizeof() random struct: 5
random.random is: 262143, random.random2 is: 511, random.random3 is: 7

Without the #pragma preprocessor directive the above code prints everything the same, except that the sizeof struct returns 8 on my machine instead of 5.

While we cannot store any values greater than those specified in the code above, if we wanted to store larger values in any of those variables we would have to change the size of the bitfields accordingly. Keep in mind that on my machine unsigned integers take up 4 bytes of memory (32 bits), and thus if I allocate more bits than a multiple of 32 (eg. 33, 65, 97, etc.) a whole extra byte will be added to the size of the struct if pragma 1 is defined. If pragma 1 is not defined then on my machine every time I go over the size of a multiple of one variable of size int, then a whole 4 bytes will be allocated until I fill those 4 bytes in which another 4 bytes will be allocated.
Strings
Strings in general programming lingo/jargon is simply an grouping of characters (words, sentences, phrases, or just a random assortment of letters “abc123!@#”). For example the text I am writing now could be a string, if it were it would contain all the spaces between the words, all the letters both upper and lowercase and any punctuation such as the period at the end of the sentence. By now, you hopefully know that the C programming language has a type called “char” that can hold a number that can correspond to an ASCII character such as a letter, number or symbol. If you just have one of those it is a char/character, but if it is grouped together with other characters, than you have what is called a string. For example ‘Z’ is a character and so are ‘A’,‘R’,’E’, and ’B’. Individually they just refer to single letters and have little meaning outside of themselves. But if combined together in a specific order they convey a meaning to a reader that refers to an animal in English (ZEBRA). Obviously C has no concept of zebras and does not know what a zebra is or how it is defined. All it understands is numbers. By now you should already have an understanding of arrays as none of this will make much sense if you do not. This is because a string in C is simply an array of chars. Strings in C are definitely where the language is the weakest. Strings in C are one of the most difficult concepts in the language for both beginners and people coming from other languages. This is because in most, but not all other programming languages strings are first or at least second class data types. Meaning they were well thought-through as the language was being created and have convenient syntax to use in an attempt to abstract away and prevent all the gotchas and gimmicks that comes with all the nitty gritty low-level concepts at hand. C almost feels like it handles strings in a bolted-on solution and was added on as an afterthought. One pet peeve I personally have when I myself was trying to wrap my hand around the concept of strings personally is the three phases you will here passed around like crazy in forum and educational resources. They are: strings, string literals, and string constants. Now depending on who is slinging these terms around they can all be used to mean identical concepts to one another or three distinct things, or strings and string literals can be the came, but string constants are different or all sorts of things. This is one of the biggest problem people have as they are learning about string in C because most people who teach it, do not fully understand strings themselves as I have proven with how inadequately people answered my stack-overflow.com and in-class questions about the subject. So if a string is just an array of chars, that seems pretty easy, what else is there to know? Well, a decent amount actually. Strings can be declared, initialized, defined, and assigned all matter of ways. Before fully understanding the following on strings, one must know in addition to arrays how memory is allocated (heap, stack, static at least). One must also know the difference between initialization, assignment, declarations and definitions. Once you understand those three concepts strings will be much easier to understand and avoid every gotcha C has to throw at you. In C, strings are always just character arrays. However, how they are stored in memory as well as how they can be defined and manipulated can vary drastically on many factors. First one, should ask after the last statement if strings are always character arrays, then why do not I just call them character arrays. The simplest answer to that question is because strings are always character arrays, but character arrays are not always strings. Strings in C, must always end with the number 0 which is also known as the character/character constant NULL which is represented as ‘\0’ using single quotes. This is because the 0 tells the compiler where the string ends, otherwise it will keep interpreting every number as a character endlessly going through all of your memory until it finds a 0/NULL. In printf it has the %s option to print strings. It goes through a character array and prints all of the corresponding characters until it hits the NULL/0 and then stops. Now imagine if you did not include a NULL in your character array, when using %s it could keep printing and searching through the memory well past the array until finally it finds a NULL/0. Examples:

Strings:
char arr[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0' };
char arr[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0 };
char arr[] = { 'h', 'e', 'l', 'l', 'o', '\0', 'w', 'o', 'r', 'l', 'd', 0 }; //technically contains two strings
char arr[12] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0 };
static char arr[12] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd' }; //static variables are always initialized to zero so this is NULL terminated
char arr[] = “double quotes adds null if size permits”;
char arr[] = "a";
char arr[] = {67, 108, 97, 121, 0};
char arr[5] = “Clay”;

Not Strings:
char arr[] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
char arr[11] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', 0};
char arr[12] = { 'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd'};
char arr[] = ‘a’;
char arr[] = { 67, 108, 97, 121};
char arr[4] = “Clay”;

If the array is not large enough, it cannot tack a NULL/0 to the end of the string which means that it will probably not be null terminated, but hypothetically you could get really lucky with the next byte being set to zero, but the odds of that are not good. Global variables, whether static or external are initialized to 0 by default meaning that arrays created here hold zero in every position except those assigned to meaning that if I had an array of size 100 (static char arr[100];) and never put anything in it, all of the values are known to us still because it automatically fills it up with zero (null). A string in c is a group of characters that ends with the last character being 0/NULL. If it does not end with ‘\0’/0/NULL then it is simply not a string, but an array of chars which is just a superset of what strings are.
Comments
Comments are lines and parts of lines in a file that are not to be included during compilation and are either just for reading by programmers to help understand the code, to prevent some code that is not yet ready for deployment to be ran or to say anything else the programmer wants. There are two types of comments: those that are prefixed with a // that span the remainder of ONLY the remainder of the line, and /* comments that can span multiple lines and will continue to be commented out (how programmers phrase inactive code) until it finds a succeeding */

// This is a single line comment
NOT COMMENTED OUT
printf("NOT COMMENTED OUT\n"); //This is commented out, but the printf function was not.
NOT COMMENTED OUT /* This is commented out
this line is commented out.
so is this line.
this is commented out. */ NOT COMMENTED OUT
NOT COMMENTED OUT

Whitespace and Tokens
Whitespace is any space or tab or the unused space not taken up by characters. C tends not to care about white space and lets the programmer use it more or less as they please for formatting. Whitespace does not matter except in strings or for identifiers and keywords. Example int is correct but 'i' and 'nt' are two tokens, i and nt, and will not compile.

printf(“%d”, 1);
is equivalent to:
printf      (     “%d”  ,           y   )            ;

Tokens on the other hand are every non-whitespace character that the compiler breaks the program into. Essentially tokens are the smallest things in C that cannot be broken up any further. There are a few classifications of tokens in C. They are: keywords, identifiers, operators, and strings. Keywords for example are not 's''t''r''u''c''t', but "struct" all one word, the word itself has meaning even if the six characters that make it do not. If your code had str uct name; on a line of code the compiler would have no idea what is going on, but if you say the word struct name; then the compiler knows that you are referring to a variable of type struct name. It has meaning, in this way so do operators and strings. The compiler knows when you use the * symbol somewhere in your code that it is either referring to the multiplication, creating a pointer variable, or dereferencing a pointer variable depending on context. Just think of tokens as the building blocks of programs, they are all the syntax and words and symbols that actually have meaning.
Branching
(return, if, else, while, for, do, continue, break, switch, case, default, goto)

return- “return OPTIONAL_EXPRESSION;” exits out of the function in which it resides. Can bring one and only one value with it that is derived from an expression.
int func1(int a, int b) {
    return a + b;
}

The above function returns the value of the expression (a + b) where the function was called from. For example somewhere in the main or another function might be a call to the function like:
int sum2Nums = func1(3, 7);

sum2Nums would now store the value of 10.

if- evaluates an expression and then either matches it to an exact case or with a range (such as < > <= >=, etc.). If the expression returns not zero/true- then it branches the code to execute whatever is on the following line or within the brackets it might come with. If the condition/expression evaluates to false/zero the following line and any code within its brackets is skipped and the code immediately following will be the next to execute in its place.
short x;
printf("Enter a number less than 10: ");
scanf("%hi", &x);

if (x > 10) {
    	fprintf(stderr, "ERROR: the number you gave me was greater than 10.\n");
    	exit(); //crashes program
}

printf("Thank you for giving me a number less than 10!\n");

In this scenario only one of the printf functions will ever execute on any single execution of the code above. The program will print "The number you gave me was greater than 10, please try again." and exit the program if the number given by the user in the scanf function was any number greater than 10, in all other situations, such as if the number they gave was less than or if the number they gave was equal to ten then the printf function saying "Thank you for giving me a number less than 10!" will print.

else- a way to branch code that must be immediately following an if statement that allows the user to have the code follow another specific path only when the aforementioned preceding “if statement” fails. Often leads directly to another if statement. This prevents the if statement from being checked if the first one was true.
short age;

printf("Enter your age: ");
scanf("%hi", &age);

if (age < 0) {
	printf("You're not even born yet.\n");
}else if (age < 13) {
	printf("You're a young child.\n");
} else if ( age < 18 ) {
	printf("You're a teenager.\n");
} else if (age < 26) {
	printf("You're a young adult.\n");
} else if (age < 35) {
	printf("You're starting to get old.\n");
} else if (age < 42) {
	printf("You're middle aged and officially old.\n");
} else {
	printf("You're ANCIENT! Your a really old geaser!\n");
}

If the user types in an age less than 0, then it means they are negative age which is not possible so it prints the message: "You're not even born yet." to the screen, if the number is greater than 0 it then proceeds to check if the number is less than 13, and if so prints the next message, but if it is greater than 13, it proceeds to check the next conditional which asks if the user is less than 18, if so it prints the message under that conditional, but if not continues to check the next else if statement and this continues until finally it either prints a message from one of the else if statements or makes it to the else statement which is a catch-all that will print every time the code makes it there. (Which is only when none of the preceding if statements were executed.) SO in this scenario the last else statement will execute only when the user gives a number that is greater than or equal to 42.

while- a type of loop that allows code within its scope to be run over and over again until a condition is evaluated to be true. Usually used when we want code to to be looped over and over indefinitely or until a spontaneous condition becomes true that will not always happen at the same time every time. While loops are usually used as event loops where it runs until a specific event/action occurs and that is when it stops, but this is obviously not how it has to be used. Its counterpart the for loop is usually what developers use when they have a set number of operations to perform.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

srand(time(0));

int win = rand() % 1000;

long long num = 0;

while (num != win) {
    	printf("Enter guess: ");
    	scanf("%lld", &num);

    	if (num > win) {
        		printf("lower\n");
    	} else if (num < win) {
		printf("higher\n");
	}
}

for- another type of loop that is traditionally used when we know exactly how many times we want the code to run or have a way of knowing. This is contrary to how while loops are usually used, which is usually where a while loop waits for a specific event to occur to stop. For loops can do this to, but convention is to use a while loop and a for loop for their respective strong suits. This loop usually iterates through code over and over a set amount of times while incrementing a counter variable until it is less than, greater than or equal to some condition.
#define TIMESTOPRINT 20
char name[20];

printf("Enter your name: ");
scanf("%19s", name);

for (int i = 0; i < TIMESTOPRINT; ++i) {
	printf("%s\n", name);
}

Prints the name given in the scanf function 20 times.

do- allows you to write the code usually contained within a while-loops brackets before evaluating the conditions, this means that the code will ALWAYS execute AT LEAST once as it runs the first time before even checking the for/while loop condition. Then it will continue to run if and only if the while/for loops conditions are true.
short num;
do{
    	printf("Enter a number greater than ten but less than 100: ");
    	scanf("%hi", &num);
} while (num > 10 && num < 100);

Will continuously ask for the user to ask for a number greater than 10, but less than 100 until they do so properly. Will always run the code inside of the do loop the first time, but if the number they give is incorrect than it will just keep looping.

continue- used inside a loop when you don't want the code immediately following to be executed during that iteration, but you simultaneously do not want to exit the loop altogether. After a continue the code will immediately go to the while/for loop conditional statement and check whether or not to stay inside the loop and then will proceed as a normal loop would.
#define LENGTH 11
for (int i = 0; i < LENGTH; ++i) {
	if ( i % 2 ) {
		printf("%i", i);
		continue;
	}
	printf("_");
}

The continue statement stops further execution of the for loop and continues to increment i and run the for loop again starting at the beginning so the program prints _1_3_5_7_9_ onto the screen. Without the continue keyword the output changes to: _1__3__5__7__9__

break- exits from the loop in which it finds itself and starts to execute the code immediately following the loop. Very similar to a function, but instead of breaking out of entire functions it just breaks out of blocks of code within functions such as loops and case statements.
#define LENGTH 11

for (int i = 0; i < LENGTH; ++i) {
	if ( i % 2 ) {
		printf("%i", i);
	}
	printf("_");
	break;
}

The break keyword is used to terminate the current branch unlike the continue keyword which just stops further executing of code, but goes back to the beginning of the loop or branch, the break keyword brings you to the end of the branch or loop and starts executing the code immediately after the branch and continues executing code as normal. The above code prints a single _ and then exits the loop.

case- used inside of a switch statement to check whether a variable matches a specific expression (case) and if it does say what code to execute and if it does not it should continue on to check the remaining case statements. See below for how to use it.

default- often used as the last case in a switch statement that is used in the scenario in which none of previous case statements are evaluated to true. See below for how to use it.

goto- a way to branch the code to "go to" another specified location within the same scope. Use an identifier followed by a : on a line by itself to specify a place to jump to by name. Usually frowned upon but can help in the case you want to exit nested loops or if statements to a specified place without hassle. See below case switch statement for how to use it.

switch- a conditional like if and else if, but specifically checks the value of a single variable and matches it against predefined conditional statements. Unlike an if statement, it does not check for multiple ranges at once. It has to be compared to a single expression. Not as good for boolean values. They are used because when they are capable they often look much cleaner and are easier to understand then nested if/else statements. Often used with the break and continue statements. It should be noted that a GCC extension does allow for comparing against a range of options using the ... operators, but this is still not great practice as an if, else statement would be better suited for these situations.
#include <stdio.h>

void add3(double a, double b, double c) {
     	printf("%lf", a + b + c);
}

void subtract3(double a, double b, double c) {
    	printf("%lf", a - b - c);
}

void multiply3(double a, double b, double c) {
    	printf("%lf", a * b * c);
}

void divide3(double a, double b, double c) {
    	printf("%lf", a / b / c);
}

int main(void) {

    	int one, two, three;
    	char userInput;

    	printf("Give me 3 numbers separated by commas: ");
    	scanf(" %d, %d, %d", &one, &two, &three);

    	INPUT:
    	printf("Type the character corresponding to the operation you want to perform.\n");
    	printf("ADD: + or A/a, Subtract: - or s/S, "
    	"Multiply: * or m/M or x/X, Divide: / or d/D\n");

    	scanf(" %c", &userInput);

    	switch (userInput) {
        	case '+':
        	case 'A':
        	case 'a':
        	add3(one, two, three);
        	break;
        	case '-':
        	case 's':
        	case 'S':
        	subtract3(one, two, three);
        	break;
        	case '*':
        	case 'm':
        	case 'M':
        	case 'x':
        	case 'X':
        	multiply3(one, two, three);
        	break;
        	case '/':
        	case 'd':
        	case 'D':
        	divide3(one,two,three);
        	break;
        	default:
        	printf("You entered an invalid operation, please try again!\n");
        	goto INPUT;
    	}

    	return 0;
}
Recursion
This is when a function in C makes a call to itself within the function. The size of the stack defines the limit to how much recursion is possible. This is because functions store their variables in the stack by default and a function within a function would just add to the variables stored in the stack rather than the usual replacing of memory that occurs when one function is finished and another function begins. Recursion is a form of nesting where a function will keep calling itself where each function relies on the call of itself to complete before it can move on. Example of recursion, the following will print: “0123456”

#include <stdio.h>

void newfun(int a)
{
    a--;

    if (a >= 1)
        newfun(a);

    printf("%d",a);
}

int main(void)
{
    newfun(7);
    return 0;
}
Nesting
Nesting is a programming concept where one branch, loop or function is within another branch loop or function. Imagine you have a loop that iterates over and over again doing some task, nesting is when during one loop it enters a loop that has to go into and exit before the parent loop can continue. This is very common, but usually only one or two times, however the more complex the problem it may require much more nesting than more simple programs. Loops are not the only nesting you will see commonly, it is extremely common to see one if statement within another if statement. When this happens, both the condition for the first as well as the condition for the second must be true to enter the block of code nested within. Nesting is a pretty easy concept at least in my opinion to wrap my head around so I will just show some examples of nesting so that maybe you can pick up on what I have been rambling about. Examples also include recursion where a function calls upon itself and is therefor a function within a function...etc.

#include <stdio.h>

int set_array_to_zero(int two_dimensional_array[], int width, height) {
	if (width > 0 ) {
		if (height > 0 ) { //nested if statement, only gets executed if the first if statement conditional was true
			for (int i = 0; i < width; ++i) {
     				for (int j = 0; j < height; ++j {  //this for loop is nested within the other for loop and thus runs in its entirety each time the parent loop runs through a single iteration thus O^2
					two_dimensional_array[i][j] = 0;
     				}
			}
     		}
     	}

}

int main(void)
{
	//run code in main function
	func1(1,2); //nested function call, func1 will run and then when it is done main function will continue
	//continue main
	int a = 6;
	printf("%d\n",a); //prints 6
	{ //nested block statment, this block is the child of the main functions block, inherits parents data, and can manipulate parents data, but new data created here such as variables only live until the end of the block (unless static keyword is used) .
		int b = 3;
		printf("%d,%d\n",a,b); //prints 6,3
		a = 4;
	}
	//printf("%d\n",b); //compiler error, b is not defined here
	printf("%d\n",a); //prints 4
}

Function-like Macros -> Macros with Arguments
Function-like macros are a more sophisticated ways of using the #define preprocessor directive in C. Remember that a #define preprocessor directive simply takes the constant/alias you give it finds it all throughout your source file and replaces it with the second argument. It is a simple search and replace. Another huge distinction between functions and function like-macros is some of their limitations in where they can be called and used. Functions can be passed by pointer as they have a place in memory. (See function pointers for more information). Because of this they differ from function-like macros in that macros are just that macros. Meaning all it really is is text replacement and there isn't any actual location in memory so it cannot be passed to a functions via pointer. This is important because it means that they cannot be passed in as functions as arguments in the same way that normal functions can.

Example:
#define REALLY_BIG_NUMBER 123456789
In the example above anywhere in the code before compilation, in the preprocessor step, the preprocessor will look for every instance of the word REALLY_BIG_NUMBER that is not held within a string literal and replaces it with 123456789. So if I had somewhere in my code that looked like this: sum = 23 + REALLY_BIG_NUMBER; after the preprocessor step in compilation is done, that same line will look like: sum = 23 + 123456789;. Function like macros work mostly the same way. In order to call a function like macro you need to include parentheses and names for parameters you want if any passed into the macro like so:

#include <stdio.h>
#define MACRO(A,Z,F) printf("%lf\n", A + Z + F)
int main(void)
{
    	double num = 1.1;
    	MACRO(32.0,7,num);
    	return 0;
}

The result would print 40.1 to the screen.
As you can tell from how I called it, that I do not have to specify the type of variable that I pass into it and I can pass multiple of different types in any order, this is both great and terrible at the same time. This is great because this leads us to one of the biggest benefits of using function like macros in that they allow for the equivalent of generics (look up what generics are if you are unfamiliar) from other languages to some extent although not quite as unlimited. The downfall, being actually the same as the upside, in that these functions do not check the types that you pass into them meaning that you lose type safety. Not having types safety meaning that if something goes wrong or the value of the variable is not holding what you expect it to, this can lead to some nasty debugging trying to figure out why your code may not be working as expected. The other main benefit that may occur that is greatly exaggerated these days (especially with how fast modern computers are and how smart and efficient compilers have become) is that macros are FASTER THAN FUNCTIONS, because they just expand out to code. Unlike a normal function, this is like any other macro in that there is not call to a function during runtime, instead the function-like code is supplanted line for line in the function that you call it in meaning there is not lookup in the symbol table, not moving to another function and creating a new stack frame, but just gets added straight to the function that you call it in.
Memory in C
Translation Unit: A single source (human written) file including any headers or other files included with the #include preprocessor directive.

Global and static variables are always initialized to 0/NULL, while auto/local and register variables are initialized to garbage (unpredictable).

Scope: The visibility of an name/identifier. Variables/structs/unions/arrays/functions and others are only visible to be called and or manipulated by code that is within the same scope as it or a child of it. You can have lots of variables with the same exact name/identifier as each other as long as they all have separate scope/visibility. Curly braces usually denote scope and if their are braces within braces, the inner braces are “children” to their outer braces and inherit all of the variables within the parent. Any changes to the parents variables in the child braces are kept for both the parent and the child. But any new variables declared in the braces/scope of the child whether it be a loop, function or just brackets is only visible within it and its children (if any), and not to the parent. Working our way up from smallest scope to largest scope is: function prototype scope which are variables that only occurs when a function prototype is declared and is not simultaneously the function definition. In the scenario that there is a function prototype the variable name is just their for convenience to the readers and writers of the code base and are not even required during prototypes. They do not even have to match the name of the variables when defined later in the code and can have the same name as any other variable and are not allowed to be used anywhere else in the code. The next smallest scope is block/automatic scope that can be explicitly created using the auto keyword, but this is the default behavior, so the auto keyword has basically never been used since the creation of C as it is mostly just an homage to its predecessor the B language. Variables created inside of curly braces, functions, loops, if/else, or switch statements are only visible within the braces they are defined in and cannot be modified or called outside of their respectable braces (by default unless using pointers). In the case that an if/else, do/while, while, switch, or for loop does not have braces (when it is only one line long this is allowed) then variable die when those loops and branches have been exited out of. Static global variables have the next highest scope as they can be used throughout the source file in any function or outside of all functions. They can not be used in other linked source files however as they have internal linkage which means they can only be used inside of their respectable translation unit. Global variables without the static keyword have the largest scope and can be used everywhere static global variables can be used and can be called by other linked translation units with the word extern in those translation units respectably. There is one other loophole with scope in that if you pass the value of a variables location in memory (a pointer) into another function then you can modify the value of variable even though the variable is out of scope, because while the second function has no reference to the name of the function if you just give it the address it can still manage to get their. Analogy: imaging I have a friend name Sally. You know me, but do not even know Sally exists. If I invite you to go to her house for a party and you agree I can do two things. I could just say okay, meet you at Sally’s at 7 and leave/hangup. And unless you put a tracker on me would have no idea how to get to Sally’s house to attend the party. But if I told you to go to 1600 Pennsylvania Avenue NW, Washington, DC 20500 and you could GPS your way there no problem. Me giving you the address is me passing the pointer to you as the function parameter. It enables you to modify variables in my function that you technically do not have the scope to do. It should be noted that variables cannot have the same identifier/name as other variables declared in the same scope as it. In the case that their are two variables of the same name with overlapping scope (if possible, given implementation) the variable with the SMALLEST scope takes precedence. Thus if you have a local/auto/block scope variable called foo and a global variable called foo, the local one has precedence. Should be noted that variables declared static inside of a block, still retain block scope. The static keyword only effects scope if used on a global variable, the static keyword is more used for talking about the lifetime of the variable. The extern keyword is basically telling the compiler not to panic immediately when the variable of its name has not been found and that it is defined elsewhere in the program, but if it is not the compiler will still throw an error. Extern goes and finds the last declaration of the variable and uses it, so if you extern a variable that is in another file and than manipulate it, and then in a deeper scope extern the same variable, you are just calling the last variable of that name that was externed and not the original.

Lifetime/persistence of a variable: There is a lot of similarity and overlap between scope and lifetime of a variable, but they are subtly different. There are three types of persistence and lifespans a variable can have. It can be global (static), automatic (stack), and dynamic (heap). Everything in a program is loaded onto and stored in RAM which is a volatile form of memory/data storage that gets erased whenever the computer powers off/loses power. In C the compiler (while being implementation defined) usually sets up the memory a program can use and divides it into a few different types. These are static memory which holds the values of variables using the static keyword, as well as any global variables. These variables are stored here because the compiler knows they will last the lifetime of the application and they are of defined size so the compiler knows exactly how much space they will always take up. Static memory can further be broken down into smaller areas, but I will have to come back after I learn about this a little bit more. The next type is local/auto/stack variables that include any other type that are declared within a function without the use of malloc, calloc, and realloc. These variables also have to be known at compile time so the compiler knows how much space to set aside. Variables held in the stack are only alive for as long as they are within scope, and as soon as their scope is left, they are freed automatically and that space can be used by another variable in the new stack frame. Variables in the stack are created in a first in last out (filo/lifo) way so that as they are created they are put on top of previously created variables in the function one right after the other in memory (stack frame). Because everything in the stack has a known size at compile time the compiler can make optimizations and can speed things up as it knows exactly where every variable is on the stack. The stack is only so big however and trouble occurs when it is used up. In the event that the stack fills up, depending on how it is implemented it might start overwriting static or heap memory leading into what is known as a stack overflow. This is fairly rare however as memory is released automatically whenever a function is finished- creating a new slate for the next function to start from. There are, however, a few occasions it might occur. When functions call other functions which call other functions, the stack is never reset as each function never finished and thus never released all of the variables in its stack frame. If too many functions are called it can lead to too much space in the stack being used and thus a stack overflow. Another time this occurs is during recursion for the same reason if the recursion goes too deep. Finally the other lifetime that exists is dynamic variables that live on the heap. Heap variables are stored manually by the user and need to be freed manually by them as well. The heap is slower than the stack, but is useful as it is the only place you can use if you don’t know how large to make a type at compile time. Instead of just creating a variable of a specific type, you can create a block of memory allocated from a large pool of memory and then store stuff their using a pointer to its location. Dynamically allocated space for variables can be created here using malloc, calloc, and realloc. When you are done using the space, you can free it up so more storage is available using the free() function. If you do not free the space after you are done using it, then it becomes wasted space and this is what is called a memory leak.
#define vs const vs enum
#define is a preprocessor macro. If you #define VAR 23 and then later in your code use VAR, then it will during preprocessing substitute all VAR with 23 like search and replace works in word processors. This has the advantage of having global scope and being really easy to use. The downsides being that you do not have scope or compiler warnings that you get with the other options. const is used like const int x = 6; You just use the const word in front of the type of the variable you are declaring. This has the advantage that the compiler can see the type and give warnings if the user is using it in a way that they should not. Const also lets you control its scope more by how and where you call it. Unlike #define however the values technically can still change which can lead to unintended consequences if used improperly and adds a lot of complexity for someone reading the code. This is true because although you cannot change the value by simple assignment you can still change const values by pointer dereferencing like every other variable. enums like const give you more control over the scope then #define, but unlike const variables enums can only be of type int while const can be any value. Enums unlike the other two can also be used to create unique types in C. This means that if you create an enumeration type and then create a variable of that type, then it is not constant in what it holds, but can only hold certain values. Think of it as an advanced constant, there is not a specific value it must contain, but their are specific values it is only allowed to contain. Enums are usually easier to understand and interpret and are easier to work with in any case where a constant number is fine.
Identifiers
Identifiers are the name in which you give a variable or entity to be used later in order to refer to it. For example int var1 = 2434; The name ‘var1’ is the identifier. If you want to use that variable later you can just use that identifier. There are limitations to what you can use as names for variables and other entities. All identifiers must start with a letter or underscore. Must be present/be of 1 character length or greater. Case sensitive (while upper and lower case letters can be used, var7, Var7, vAr7, vaR7, VAr7, VaR7, vAR7 are all different variable names and would not refer to the same memory location.) It is best practice to use identifiers that are appropriate and may give a hint as to what its value/purpose is in the code.
Processes
Threads and processes are some of the most powerful tools in a programmers arsenal. With this power comes with a great deal of complexity and with this complexity. With this complexity comes bugs, problems and errors that are easy to make and hard to find. Simply put- processes and threads are both branches in the code that can run simultaneously. However they differ in scope. Before we go over these differences it should be noted how confusing the definitions of these ideas can be, it should be made clear this is "implementation defined" hell. In fact a thread on one machine might be referring to what you think of as a process in another and visa versa. With this ambiguity has led to so many problems in different implementations. Over the years there has been a decent amount of standardization in these definitions, but a lot of old code and even under the hood on many Unix operating systems they still have not quite caught up in this regard. Think of a program as a recipe book with lots of recipes to choose from and when one is chosen it goes through the list of instructions in order to get the desired result. Sometimes, especially in early programming you chose a single recipe in which itself was also simple, all you had to do was follow all of the steps one right after the other and then you end up with the finished product in the end. This is all fine and good, but if you think of a program as a recipe book sometimes it makes more sense to do multiple steps simultaneously in order to save time, sometimes you may even work on multiple recipes concurrently- also for the sake of saving time. Even further sometimes you split the workload to other friends and/or family members and assign them to certain recipes and have them show up to the meal with their dishes to add to your own for big meals. When you wrote a single program, such as one that prints "Hello World" onto the screen this program had a single process as well as a single thread. If you are for example making a bacon egg and cheese sandwich on toast for breakfast, you have one process (the making of breakfast), but you can cook the eggs and bacon simultaneously while the bread is in the toaster. In this example you can split the task up into multiple threads, one thread for making the toast, and one thread each for the bacon and the eggs. Now note that the cheese is not being cooked and thus is just waiting in limbo as it requires at least the bread to be toasted before you can do anything with it. So for this example you have one thread work on making the eggs, one thread work on making the bacon and one thread work on making the toast, and when they are done you combine their efforts into the sandwich. Now consider another example such as being in charge of cooking a large family's thanksgiving dinner. Now you are not dealing with just one recipe that can be done in several ways, but MULTIPLE recipes that each themselves can be done in multiple ways. Now think of the turkey, the stuffing, the mashed potatoes and gravy, the ham, the pumpkin pie. This could be tackled in many ways, now like the bacon egg and cheese example some of these recipes surely have some overlapping ingredients, and this is where the problems occur. What if multiple recipes like the stuffing and the mashed potatoes both call for butter? Now imagine your mashed potatoes thread asks an imaginary butler first if there is any butter available and if so how much, the butler goes to the fridge figures out how much butter there is and then reports back to the mashed potatoes how much was there. As that was happening another thread asked another butler how much butter was in the fridge to see if their was enough for him. This butler goes and checks the fridge as well and as he is telling the second thread the first thread claims all of the butter. Unfortunately now, when the second thread asks for the butter that he thinks is there, an error will occur. This is where locks, mutexes and semaphores come in to play and prevent this scenario. However, before we get into that imagine now that instead of the same person making both the mashed potatoes and the stuffing it was split between two friends who lived separately. Now there is no overlapping recipes needing eggs as they are pulling out of two separate fridges/kitchens (this is separate processes). Having multiple people from separate houses make different dishes is another great way to split the work because then the resources are not shared, but this comes with its own downside. This is more cumbersome which is why processes are often called (heavier) than threads, because now you have to make sure the other person is on the same page and knows what everyone else is allergic to to make sure he does not include any allergens and that he does not create something someone else is already planning on bringing. The communication is clearly not as great as if you just did everything yourself, but you also don't have to work as hard. Processes like this scenario have their own location set aside in memory and do not share this memory with other processes. Processes are created with the clone, fork and exec functions (there are a lot of slightly different exec functions and you should read up on their differences to determine which one better suits your needs). It should be noted that I am not talking about the clone and fork (which uses clone) system call, I am talking about the functions found on all POSIX platforms in the unistd.h header file. When one creates a process in the most common way, which is using the fork() function what is really happening is the clone system call is being used to make an exact replica of the process, but give it another process ID and another area in memory to play with. This is contrary to the exec function which replaces the current process with another. This means that the new process will use the same space in memory as the parent in which calls the exec function. This means that if you fork a process and then use the exec function, the exec function will replace that second process and you can use it to execute another program if you want. This is extremely powerful as it allows you to call other external programs within your program and have them run concurrently with your program. To allow even more features. So essentially you have identical programs, with different locations in memory, you can then use a variable to store the result of the fork function, which returns the process ID of the forked process. It should be noted again that it is important to learn about PPID (Parent Process ID), PID (Process ID), and TGID (Thread-Group ID). While it is important to learn what these are, this is again implementation defined and because of the lack of standardization and the willing to play nicely of many projects sometimes these definitions get mixed up and what one considers a TGID another considers a PID and so forth which is really stupid, but so are most program language designers and programmers. A process that creates another process is called the parent process, the process that was created is the child process (just like in humans). Their can be multiple generations of processes, in fact that is very common. A process that makes a process which itself makes another. Every process has a unique PID (process identifier) and almost always* (implementation defined) the child process will have a larger number PID then the parent as it was created later. A return of -1 or any negative number for that matter from the fork() function means that their was an error when creating the process. A child process has a PPID which is used to identify which process it is a child of. The PID and PPID can be obtained with the getpid() and gitppid() functions respectively. If the parent process requires that one of its child processes have finished before moving any further in their execution, this is where the wait() and waitpid() functions come into play. Wait() can be used when a parent has a single child, while the waitpid() is used when the parent has multiple child processes and needs to wait for a specific one to finish before continuing. The fork() function when it is called returns the PID of the forked process which can be captured in an assignment to a variable. Every process in Unix has a PPID including the first process of our programs except the init system of the operating system you are using. Fork returns 0 for the child process and a number greater than 0 for the parent. You can use this as an if statement to split what each process works on. If you have an if statement like:

int pid = fork();

if (pid = -1) {

	//check if the fork failed, and if it did this code will run

}

if (pid == 0) {

    //have the child process do this simultaneously

} else {

    //have the parent process do this simultaneously

}

This can be nested as much as needed for the amount of processes and nested processes you have running in your program. Keeping track of all of this is hard, but the more you do it the easier it gets. Again, its worth repeating that the variables in each process belong just to that process and if you change them they are only changed in that process. If you want to have communication between processes, you should first look into if threads are a better option, and then into sockets, shared memory, pipes and named pipes. But because usually the variables are all unrelated to the other processes you do not have race conditions and too many people vying for the same ingredients in the fridge.
Threads
Atomic operations are those that cannot fail midway through. They either never begin or they finish, and this all occurs before anything else can take place with the variable. They are operations that start and finish in "one-step" this allows us to make sure that if two processes/threads try to read and write to a variable that one will not read then begin to write as the other one starts reading giving it bad information about what is held within the variable. There is the _Atomic keyword in C starting after the C11 standard and there is stdatomic.h that has functions and other things that help create atomic structures in C. _Atomic keyword and the subsequent functions don't protect us entirely though as a lot of concurrency issues and race conditions occur over the span of several lines of codes. Often times variables need to be used a few times and not immediately for a function to properly work. Imagine if one thread set a variable to something and then expected it to still be set when it used it 10 lines of code later, but in the meantime a new thread overwrote it with something else. And those problems will be answered below.


Now that we have gone over processes, its time to go over threads, honestly threads are considered "lighter" than processes, but I think this is an over-simplification. Threads are further separation of sequences of code within a single process. Think of threads as very similar to processes, but they all share memory with one another unlike processes. Again, this is where we are pulling all the ingredients from the same fridge, and thus race conditions can occur, which is where two threads try to change the value of a single variable and write over each other messing up the real count. There is also more theory, and background information that I think is necessary to understand as well as many more keywords and functions used to get around the aforementioned problems. Threads do, however, have the huge advantage that communication and the sharing of data between one thread and another is much easier. Threads are often touted for being lighter on resources and faster than processes, but this really depends on a lot of implementation factors, and with most modern compilers using copy-on-write (COW) these days with processes I am not sure how much of a difference they really make. It should be noted that when C was created their was no library or built in support for threads and that only after people begged and pleaded and even made their own APIs for the capability did the C standard finally add multi-threading support into the standard library with the <threads.h> header being added in C11. Due to this late adoption to the C standard this is one of the situations where the standard library header is actually significantly less popular than its POSIX and other library counterparts. This is why when working with threads <pthread.h> from the POSIX library is much more common. The <threads.h> header is one of the most controversial headers in the C Standard Library and I suggest you look up why if you at all care. Note from here on out I will be focusing on threads on POSIX or at least Unix-like operating systems. With this information, keep in mind that Windows at times handles and works with threads much differently and at times the exact same way. I will not cover Windows threads. It should also be noted now that while the purpose of threads is to concurrently go through code in the program on your computer that this does not always lead to faster performance and can sometimes significantly decrease the speed in which it takes to complete a task. To understand why this is the case I would look up how multi-threading and how concurrency vs parallelism works. Essentially a lot of the times threading results in concurrency and not parallelism because the hardware of the computer cannot support X amount of threads, to avoid this it is important that the total number of threads you create does not exceed the amount of processing units your CPU can use simultaneously or your program will greatly suffer in performance. Things to know about threads, because all of the threads are pulling from the same refrigerator, they all have the same PROCESS ID as one another. Threads are WITHIN processes. You can have multiple processes in your program each with multiple threads that can communicate with every other thread within that process. Threads are created with the pthread_create() function and unlike processes need to be directly given a specific task (function) to complete. This is contrary to processes which just continue down the code like normal executing one line right after the other and only after a conditional statement targeting their specific PID do they branch off. Threads are branched immediately and given a function which is theirs. Because of this all threads have their own stack memory, but share heap and static memory, meaning that heap and static memory can be some of the most dangerous to use with threads. While threads do share their PID with one another we can still differentiate them because threads have their own TID (thread IDs), again, if you recall in the processes section (this is all completely whack and complicated so just take all this as a generalization because sometimes people swap what a thread id and a process ID is). The ID of the thread to differentiate it from the others for use in branching can be found using the pthread_self() function which finds the thread-ID of the calling thread or the pthread_equal() and subsequently compares two thread IDs. From this point forward though we are going to pretend that the world is sane and perfect and pretend that thread id's are always the unique identifier between threads and that they always share a process id because that is the most common situation. To create a thread first you must make a variable of the pthread_t type. Then you use the pthread_create() function and pass in the name of the thread variable, any attributes about it (NULL for defaults which is the most common), the address of the function where it should start executing code, and the arguments needed for that function but they must be type casted as void pointers if present or NULL if there isn't any parameters/arguments. Maybe use a struct for multi-variable functions. (You should always check the return value after pthread_create that it was successful and handle it if not) So as you can tell a variable of pthread_t must be declared before this as well as the function that you want it to start executing. Then like a normal piece of code calling a function, it will start going through that function line by line concurrently to its parent thread that will be running in the function that the thread was created in. pthread_self() function returns the thread id of the current thread and can be used similar to PID in processes in functions to have separate threads run separate parts of the code. In the main process that the threads were created in usually you will find a pthread_join() function before the final return/exit of the code. This is used to make sure the parent does not die before the children finish doing whatever it is they are supposed to finish, because when the parent dies, so does its children. The pthread_join() function asks for the thread ID of the thread that the parent is waiting for and the parent will wait and not execute another line until that happens. While this is great in most cases this can lead to a problem if the child thread were to ever get stuck, as now the parent would get stuck as well. Now, if the child is waiting for the parent or another thread to finish for some reason and it never will, then this is a deadlock and the program will never finish. Waiting on threads is the hardest and most complex part of threads and so many techniques have been created to help make this easier, but it is still very complicated. This is where mutexes and semaphores and locks come into play. Locks do not really exist by themselves and are actually just mutexes. (someone correct me if this is not the case or is implementation defined). Simply put mutexes are "locks" that prevent a part of code from being run from anyone who does not possess the lock for area of the code. This is along the idea of OWNERSHIP. The area of code that is protected from multiple threads and/or processes from executing it at the same time whether it is protected via locks/mutexes or semaphores or any other condition is called the "critical section". This is because it is the part of the code that the user deems that if too many threads were to access it at the same time they could create a race condition that could negatively effect the correctness of the code and the variables held therein. So the most common analogy I have seen on the internet is a toilet-analogy that explains the differences between mutexes and semaphores, while I think the analogy is okay I think it leaves out a lot to be desired when it comes to differentiating mutexes and semaphores. For that reason you will have to look up that analogy on your own, but hopefully you find my analogies better. First lets cover mutexes before semaphores. I do think bathrooms are the perfect setting, however, so I myself will stick to it in my example. Think of porta potties, they are meant for single person use. While bathrooms come in all different shapes and sizes with single toilets or many toilets, a porta potty always just has enough space for a single person and usually just one toilet (although sometimes it includes a urinal) anyways the point is that only one person can enter, and when they enter they can now flip the lock that is only accessible from the inside. There is no exterior key, the only way realistically the porta potty is going to become available again, is if the same person who entered the porta potty unlocks the door and allows/gives the next person in line access and thus transfers ownership of the porta potty and its lock to them where subsequently only they have access to it when they lock the door until they unlock it and pass it to the next person (thread) waiting in line. A mutex allows a single thread to access a critical section in the code. This prevents other threads from changing things at the same time and messing up the variables. There are a few types of mutexes to look out for: recursive mutexes, read/write mutexes. A recursive mutex is when one thread owns multiple locks at a single time. This means that in order for another thread to enter that critical section they must obtain ALL of the corresponding locks to enter. This is usually a bug in code because for every mutex lock you must unlock that same amount of times, but this does make sense in recursive functions and thus in recursive functions there are times when recursive locks makes sense to prevent the thread from writing all over itself. Reader/writer mutexes (often referred to as shared and exclusive mutexes) are two types of mutexes that are often used in conjunction with one another. Read and write mutexes are commonly used when dealing with files. This is when you split the problem into two parts and two functions. One for reading and gathering information and another for writing to and deleting contents of the file. (This is the producer-consumer problem which is a famous concept that requires mutexes) The reader function is less strict than the writer function. This is because a writer function must make sure there is nobody reading or writing to the file or else they might corrupt the data as they write to it. Thus the writer waits until nobody is reading OR writing to the file before locking the file down with a lock for their use only and doing whatever they need to do before finally freeing the file back up to be read or written too. The reader on the other hand does not mind if other threads are also reading the file as they are not manipulating its content and thus it wown have to be worried about other readers modifying it and changing their function. Thus the read function can continue to work as long as only other read threads/functions are using the variable or file that it is after. Mutexes are created with the pthread_mutex_t which creates a variable of that type. It can be initialized to PTHREAD_MUTEX_INITIALIZER for sensible default values. After creating a mutex variable you need to use the pthread_mutex_init() function and pass in the mutex you created as the first parameter and any attributes you want to set for it, or NULL as the second parameter to declare the defaults for the mutex (most common). Mutexes are then used in the function where threads are called to and that might start writing over each other. pthread_mutex_lock() function is then used by the first thread that reaches it and takes ownership of the lock and is the only thread that is allowed in the critical section of code between the pthread_mutex_lock() function and any subsequent pthread_mutex_unlock() functions. All operations that occur between the time that those two functions are called can only happen by a single thread in a process at a time. As you can imagine when you are done dealing with the part of the code that is dangerous for multiple threads you call the pthread_mutex_unlock() function which gives up the lock and gives it to the next thread in line that is waiting for the key to the lock before it can enter the critical section (if there is any threads waiting). When you are completely done with the critical section in questioning one can call the pthread_mutex_destroy() function which takes the mutex lock as its parameter and gets rid of it clearing up the space it takes from memory and disabling its use.


Now what about semaphores? Think of a semaphore as an online video game lobby or a ride at the fair. There is no key, because there is no lock. You are simply waiting to be permitted on the ride. You have to wait until any seat opens up. You are not waiting for a specific seat, just the first one to open up when you are the next person in line. You are not waiting for a specific key that John Doe has to give you to enter, you just need "any key", just enough available spots for you to open up to enter. As soon as any single person leaves or exits the ride regardless of the reason, the next person in line (the queue) gets to take their spot and this continues indefinitely until a situation occurs that no one is wanting to ride anymore or the park closes. Unlike mutexes semaphores have no concept of ownership, a mutex had a lock that prevented another thread from entering without that specific lock, while a semaphore just needed to hear the okay to enter. Essentially their was no HARD boundary stopping the semaphore from entering, it just simply had to be let in, while in the mutex scenario their is the physical door and lock preventing access. In the mutex only the person in the porta potty can allow the next person in line access by unlocking the door, but in the semaphore any you just needed one seat to open up, regardless of who it is. It should be noted that semaphores can be binary semaphore which like mutexes allow only a single thread access to a critical section. Now you might be wondering what makes a binary thread different from a mutex at that point, and the answer would simply be the "how the access is granted" in a mutex- a single lock is passed around and only the variable that owns the lock has access to the critical section. Whereas for semaphores the process/thread waiting to enter the critical section just needs the signal (sem_post()) saying that they can enter. Semaphores do this with the aforementioned sem_post() function and the sem_wait() function. The semaphore is created using the sem_t type followed by a sem_init() function call with the name of the semaphore variable as an argument as well as two other arguments that specify whether the semaphore is shared between threads or processes and a number that will correspond with how many threads/processes it allows in the critical section at a time (or at least how many times sem_post() can be called before it reaches 0) which would make this a great time to explain that sem_post() and sem_wait() functions simply increment and decrement the semaphore value respectfully. When the semaphore is 0 that means that the next thread/process in the queue has to wait until it is a number grater than 0 to enter the critical section and just waits until then. Semaphores can be removed/deleted with the sem_destroy() function. sem_getvalue() allows you to know what the current value is of the semaphore. If the semaphore is less than the maximum allowed number specified in sem_init then that means that the process/thread that wants to do something is allowed to do something after using sem_wait() which checks that the semaphore is greater than 0 (allowed to run) and if it is then decrements the semaphore and runs the critical section. sem_post() does the inverse, it checks that the variable is less than the maximum specified in the sem_init function and if it is then it increments the variable by one. There are two types of semaphores, counting and binary. Binary semaphores are similar to mutexes in that they only allow one thread to enter the critical section at a time, but it differs in how it decides when to let the next thread enter. All it requires is a sem_post to be called to allow the next thread to enter regardless of what thread or process says the sem_post(), this is contrary to mutexes where only the one with the lock can be the one to free it. Counting mutexes allow up to a certain amount of threads to access the critical section, but as you now might expect this does not prevent concurrency errors from the threads inside of the critical section, if you allow for example three threads into the critical section of your code with a semaphore they can still have race conditions within that critical section unless further mutexes or semaphores are used to prevent such a thing. While binary semaphores might seem like mutex replacements and can be used just like mutexes, they can be signaled by any other thread and thus are more flexible, but less secure than mutexes that require the lock owner to give it up. Finishing/terminating/killing/canceling threads is a way of handling threads in C and finishing them up. The pthread_join has a second parameter which can be used to get the return value from the thread when it exits OR returns from the function. A thread exits its function in a couple of ways, if it makes it to the end of the function and their is a return keyword it can be use to pass a single value that can be accessed using the pthread_join function. pthread_exit() is another function that terminates the calling thread and accepts one parameter also being the return value that a waiting pthread_join can use. Another way to pass things from threads is the simplest, which is to use global variables which are stored in static memory and accessible from all of the threads. This comes with all of the usual benefits and downfalls of global variables. Pthread_cancel cancel can be called from any other thread in a process on itself or another thread, and this is the polite way of asking it to finish up and depending on its pthread_setcancelstate() and pthread_setcanceltype() will determine how these work. Another way to terminate a function is with the age old signals. Signals can be passed to threads using the pthread_kill() function and like any other signal function it can do the defaults or have its own signal handling on every signal except SIGKILL(9) which will always terminate the thread without fail. The thread can kill itself or another thread in the process can kill it using this method.


Barriers are one of the last concepts in threads that I want to talk about, but thankfully if you have a good grasp on the previous concepts- then barriers should be relatively straight forward. Barriers are another method of making threads wait and then enter a critical section, but they are in a sense contrary to everything else we have talked about. Unlike every other synchronization primative which is just a fancy way of saying objects that allow us to work with threads safely, barriers prevent threads from entering the critical section UNTIL x amount of them want to. So using this object allows more than 1 thread into the critical section always. Think of a video game where you have a lobby while people are connecting to it to play the game, you do not want to start the game before enough players are in the lobby to have a full game. While you may not need everyone to be there at the creation of the game you may need at least three players before the game starts or something like that. Well in this case the barrier would wait until at least three threads are waiting at the barrier and then only then will it allow them into the critical section. Then it will lock back up until another three are waiting to enter, hopefully you can also see the problems that might occur using this object as then you will always need three people to enter and if you have four people then one person will be left waiting. Barriers are created like every other object in the <pthread.h> library, using pthread_barrier_t to create a variable that then need to be initialized using the pthread_barrier_init() function before it can be used. Which takes three arguments being the reference to the variable of barrier_t type, the attributes of that variable (NULL for default), and an integer for the number of threads you want the barrier to wait for before allowing the threads to pass. Then in order to use it, you have to use the pthread_barrier_wait() function which takes a reference from the barrier_t variable and it is now usable. When you are done using the barrier, you can call pthread_barrier_destroy() function which will remove any reference to it.


Conditional variables might be the last thing I want to cover about threads, because the threads topic is way too large to cover in its entirety. Conditional variables are quite complex and at least require an decent understanding of mutexes and how they work. Sometimes a situation may occur in which you only want a thread to execute a piece of code in a critical section when both it has the lock/key to enter the critical section AS WELL AS after a specific condition has been met on top of that (especially one that is dependent on a condition that occurs in another critical section (maybe by another thread)). Essentially when you have two critical sections that mutually require something from one another (read/write, consumer/producer problem but more complex). This is when the area in the critical section would not do what you want it to do unless another requirement is met before that code gets executed. You need to be able to enter the first critical section of code to complete the condition, then relock that mutex, unlock the other mutex so that a thread can enter it to check if the condition has been met and then lock that mutex. Condition variables are here for the rescue in this situation as well as one more, which is spin locks, no don't worry if you do not understand what I have just written as conditional variables are something that are very very complicated and makes more sense in practice with examples then with words at times. Essentially a spinlock is a very inefficient way for a thread to wait for a lock to be unlocked, like an infinite while loop just waiting for the mutex to unlock and until it allows the thread waiting to get into the critical section just goes in the loop non-stop taking up CPU cycles unnecessarily. Conditional variables solve this by adding a signaling feature to the mutex in a sense that allows a thread to just wait without doing anything for a mutex lock to unlock for it to enter the critical section and the conditional variable will signal for it to wake up when the condition has changed and it can enter the critical section to see if the condition has been met and it can continue to do its work. Conditional variables are created using the pthread_cond_t type and can be assigned to PTHREAD_COND_INITIALIZER for default attributes. It then like most things in the pthreads library needs to be initialized using the pthread_cond_init() function which takes two arguments being the conditional variable that it is initializing and any attributes it has or NULL for default (most common). Conditional variables are very closely tied to mutexes and are almost exclusively used WITHIN a mutex lock. The next step in conditionals is to use the pthread_cond_wait() function which takes the address of the conditional variable created before as well as the address of the mutex lock that this function is held within. Because of this I like to picture them as a cell door in a prison. Most inmates and prisons allow inmates to leave their cells to go to common areas of the prison such as a cafeteria or outside to spend time with other inmates to prevent them from being isolated all day. In order to get to the yard to get some fresh air the guards have to allow the prisoner to leave his cell after opening his gate, but just because his cell door was opened it does not mean they are a free person, their are still even bigger doors and walls standing between them and their freedom, but they were allowed a little bit of freedom to walk to the outside or to the cafeteria. A conditional variable is like this as it allows a second thread to access a critical section that is locked using the same mutex lock and just allows it to check or manipulate a conditional variable that is used by another thread. That is it, its allowed into the critical section, but only to deal with the conditional variable. And as soon is it is done with the conditional variable that mutex is locked back down and no other thread is given access. SO conditional variables allow a second thread into a mutex lock with a specific purpose in mind that it manipulates said conditional variable in some way, and when it does it signals another thread that the condition has changed and that variable goes into the critical section solely to check if the condition has been met and if has it proceeds in the critical section and if not it re-locks the critical section and waits again for another signal. Now a signal is given with the pthread_cond_signal() function with the only parameter being the address of the conditional variable itself. The signal allows ONE other thread waiting for the mutex lock to get into the critical section to check if the condition held within the mutex lock is met, but their is another function which is pthread_cond_broadcast() which is just like the pthread_cond_signal() function except that it allows ALL threads waiting for the mutex_lock to check and see if the conditional inside of the mutex has been met for their needs and then one by one they go into the critical section, lock it up so only they can be in there one at a time, do what they need to do and proceed to unlock and lot the next thread in one at a time until they have all made it through or until the condition is no longer met for the incoming thread leading it to wait until it gets another signal that the condition has changed and that maybe it will be met inside the mutex lock again. I know this is not a great explanation, but conditional variables are hard to explain, and need to be tested, seen and used to really understand them. Like other things in the pthread.h library conditional variables can be removed with the pthread_cond_destroy() function which takes the conditional variable as a parameter and deletes any mention of it from scope.


It should now be discussed that in order to use threads in C often times a compiler flag must be used to allow linking to the threading library. Often times you will see the use of -lpthread and -pthread, as far as I can tell the -pthread option links and adds some very useful preprocessor stuff to get all of the use out of threads including other libraries and the -lpthread is an antiquated legacy thing that after the year 2005 is no longer as good as just using -pthread??? I do not really know tbh, just use -pthread. Or look this up on your own.


pthread_once() function can be used to prevent multiple threads in a process from overwriting each other by allowing only a single thread to call that function and then will deny any other thread from calling it again.


Starvation is a term that you might here with threads that has to do with priority of access for a critical section. Imagine you set up a critical section that is prioritized to certain threads over others, if for some reason there is a function that is just continuously making these threads that have higher priority the threads with the lower priority would just be kept waiting indefinitely and this is called starvation, while they technically could* get executed in the future and not in deadlock with 0 hope, it is not looking good for them.


A thread pool is a data structure in which as the amount of work needed to be done scales up, so does the number of threads that are created to do the work. And visa versa as the number of tasks that need to get done dwindles, so does the number of threads.
Binding and Namespaces
Binding and name spaces are object-oriented programming concepts and concepts that C++ developers are surely quite familiar with. However if you are curious about these two subjects in C, they do technically have them, but in a much more simplified way. In brief C never used the nomenclature namespaces, but there are in a sense two namespaces in C. Struct and union namespace and every other identifiers namespace. This is why when you are creating a struct or union variable you have to use the struct and union keyword everytime you want to make a variable of a previously defined structure type. Oftentimes to combat this egregiously painful no-good horrible habit of having to type the word "struct" or "union" when creating variables of these types- people often use the " typedef " keyword, make it so they can just use the identifier they want instead of struct and then the identifier. DO NOT DO THIS BY DEFAULT! I'm not going to tell you to never typedef structs or unions, but it should be fairly rare. The only time you should typedef a struct or union is if you are intentionally trying to make an opaque/private data type like the " FILE " keyword in <stdio.h>. In ALL other scenarios just use the struct keyword so when other people have to look at your code they know that you are using one of your defined structs or unions and this information is not hidden to them. If you want a language that hides/doesn't require the use of types, then use a great dynamically typed language like Lisp.

As far as bindings go, C does not really have a concept of binding and essentially everything is statically binded in C. Binding is the act of converting identifiers to the actual address in memory that the identifier is referring too. In C++ polymorphism which is not available in C allows for more types of Binding, but C does not.
Variadic Functions
Variadic functions are simply functions that have at least one parameter, but allow for a fluctuating number of arguments to be passed to it. By "fluctuating" I simply mean that each and every call to the same function does not require the same amount of arguments to be passed to it. Two of the most popular functions in every C programmers arsenal are variadic functions. These being scanf and printf. In both of these functions there is no limit to how many variables or constants can be passed to them. There are a couple of caveats and problems with variadic functions that you must be aware of before you just run out there and make all of your functions variadic. There is no built-in way to determine how many parameters have actually been passed into the function. This is a huge problem that has to be overcome manually. Often times when people write variadic equations- the first argument they pass in is the number of arguments they are passing in, this is definitely a good approach. This is, however, not the only approach. As you can probably figure out that neither printf or scanf requires that you tell them explicitly how many arguments you are passing in. Although if you really think about it you kinda do. Whenever you use a format specifier you are saying that you are expecting there to be a variable or constant after the format string that you want to associate with that format specifier. Although as you will see below in the examples, printf and scanf have no way of checking if you pass in more variables or constants than you said you would in the format string. This means that those expressions, variables, and or constants will be evaluated, but then ignored and not used in the format string. (This will all make more sense with the examples below) The second limitation I am going to mention is that the function must take at MINIMUM one parameter, so essentially variadic functions can vary between 1-infinity, but can never not have any arguments passed to them. As mentioned before this first parameter is often used as a int to tell the variadic function exactly how many arguments were passed into the function, but that is not a requirement by any means, just convention. To create a variadic function we do so like any other function, but then when prototyping/defining the function, in the parameter list, we use a '...' also called an ellipsis that means 0 or more arguments from this point on. Often times this is the second parameter, but can be any parameter after the first, and all of the parameters before it are required for every invocation of the function, and it signifies that any number of parameters can follow. va_list, va_start, va_arg, va_end are the other remaining (functions/macros/typedef structs depending on implementation) that you must know and are used in every variadic function. Optionally there is also the va_copy which is not required. va_list is a 'type' (a typedef struct) that holds information about the variadic functions arguments that were passed in. You need to create a variable of this type that will later be passed into the following functions/macros. va_start is a functions or function-like macro that takes the va_list takes the variable you created with va_list that created a pointer type, and takes a second argument which is the last required parameter/function prior to the ellipsis in the function definition. It needs this to know where the 0+ number of other arguments would begin. va_arg function or function-like macro also takes two arguments and is used whenever you actually want to get the next argument/its value. Every time this function is called it grabs the next parameter/argument and thus can be called in a loop to get all of the variables and then store them in a variable or array as you please. So each successive call to va_arg grabs the next argument in the list, but it has no concept of what type they are or how many their are so you have to typecast them and store them in the appropriate variable types as needed as well as have a way to determine how many times to call va_arg() to get all of the desired parameters and not ask for too many. When you are done getting all of your values you can now call va_end() function or function-like macro and pass into it your va_list variable which will subsequently end the variadic part of the variadic function. Then everything about this function from this point on is no different than any other function. va_end, does not delete any variable, it simply does some background cleaning, so you can continue on with your function and use your values as you please. In fact in a lot of implementations va_end is not required, but this is implementation defined.

Examples:
#include <stdio.h>
#include <stdarg.h>
#include <string.h>

//Function Prototypes
double add(int num_args, ...);
void printArray(int num_args, char** types, ...);

int main(void)
{
	int v = 6, w = 3, x = 1, y = 4, z = 2;
	double sum;

	sum = add(5,v,w,x,y,z);

	printf("main function end: %lf\n",sum);

	printf("%d\n",22,v+1);
	//I included the above printf to show that this code is valid and will compile.
	//While this code does give warnings, there is no error and the program runs as if the 23 was not there.
	//the expression still occur, but the printf does nothing else with them

	char a = 'a';
	short b = 69;
	int c = 7;
	long d = 3009;
	long long e = 4567889;
	float f = 3.1;
	double g = 6.402;

	char* array1[11] = {
		"char",
		"short",
		"int",
		"long",
		"long long",
		"float",
		"double"
	};

	printf("%c, %hu, %d, %ld, %lld, %f, %lf\n",a,b,c,d,e,f,g);

	printArray(7, array1, a, b, c, d, e, f, g);


	return 0;
}

void printArray(int num_args, char** types,...)
{
	va_list list_of_args;
	va_start(list_of_args, types);

	for (int i = 0; i < num_args; ++i) {

		if (!strcmp(types[i], "int")) {
			printf("INTEGER: %d", va_arg(list_of_args, int));


		} else if (!strcmp(types[i], "long long")) {
			printf("LONG: %lld", va_arg(list_of_args, long long));


		} else if (!strcmp(types[i], "long")) {
			printf("LONG LONG: %ld", va_arg(list_of_args, long));


		} else if (!strcmp(types[i], "short")) {
			printf("SHORT: %d", va_arg(list_of_args, int));


		} else if (!strcmp(types[i], "char")) {
			printf("CHAR: %c", va_arg(list_of_args, int));


		} else if (!strcmp(types[i], "float")) {
			printf("FLOAT: %lf", va_arg(list_of_args, double));


		} else if (!strcmp(types[i], "double")) {
			printf("DOUBLE: %lf", va_arg(list_of_args, double));


		} else {
			fprintf(stderr,"ERROR");
		}
		printf("\n");
	}
	va_end(list_of_args);
}

double add(int num_args, ...)
{
	va_list my_list;

	double i = 0, sum = 0;

	va_start(my_list, num_args);


	for (i = 0; i < num_args; ++i) {
		printf("%lf\n",sum += va_arg(my_list, int));
	}

	va_end(my_list);

	return sum;
}
Dynamic Memory Allocation
Dynamic memory allocation is setting aside space in a special part of RAM called the heap. It is called dynamic, because it is set aside at runtime during the execution of the program with the malloc(), realloc() or the calloc() function. (The only time the heap is used for memory). Unlike static memory which lasts through the entirety of the program, and stack memory which lasts until the end of the function in which the memory was allocated, heap memory exists until it is freed explicitly by the programmer in the code. This can occur in the same function that it was created in or much later in the program. Memory is freed with the free() function. There are two important reasons to use the heap memory over the alternatives and that is for when you either do not know how much memory you need until runtime or if you know you need space for something really big and you do not want to or even cannot hold all of it in the stack. The size of the stack is implementation defined and really depends on the resources of the computer and the operating system among many other factors. So if you are creating large objects such as really big arrays, structs or other data objects or just a lot of smaller types of data it might make sense to store them on the heap. The heap is slower than static and stack memory meaning that on underpowered computers and programs with heavy reliance on the heap over the stack may have performance issues. If the programmer does not free the memory explicitly this is a memory leak as that part of memory will not be able to be allocated later again until it is freed or until the program ends. This leads to problems if programmers are messy with their memory allocations and uses of free. Memory is often allocated with the malloc() function which stands for memory allocation. It accepts one argument which is the size (in Bytes) you want to allocate. Often times people use an expression including the sizeof() operator and put the name of a type, variable or structure in it such as sizeof(int) followed by a multiplication of a number which will allocate enough space for that number of ints (or type/object that you used) in memory. malloc() returns a pointer to the location in memory that it allocates or NULL/0 if it is unable to find and allocate enough memory. It should also be kept in mind that malloc allocates the memory in continuous blocks (at least as far as we can tell, technically the host operating system does all of the real memory allocating, but as far as the programmer is concerned with pointer arithmetic it is continuous). It is undefined what the memory will be initialized to by default. If you want to make sure everything is initialized to 0/NULL by default that is what the calloc() function does, which allocates memory exactly like malloc with a couple of caveats. First is that it actually takes two arguments rather than just one like malloc() does, but it is essentially the same information, the first argument is the number you want to multiply by, and the second argument is the size. So instead of sizeof(float) * 3, you would do calloc(3, sizeof(float)). Because of the fact that calloc also initializes all of the data to 0/NULL after also allocating the space, it is obviously going to be slower and less efficient than malloc if that is not something that matters to you. Realloc is another function when dealing with memory allocation. Realloc can only be used after a previous calloc or malloc function call. Realloc does a few different things, it allocates the amount of memory specified by the second of its two arguments, copies over contents of the allocated space that is pointed to by the pointer used as the first argument passed into the function. A few things to note, that realloc finds another area of memory of the size specified and returns NULL if it cannot and leaves the other allocated memory alone in this case. If it does find enough memory as specified by the second argument then it proceeds to copy the data from the first arguments heap memory into this new area. If the realloc is smaller than the original malloc/calloc of the pointer, then it truncates/loses all of the information past the new size, if it is larger than again it is implementation defined as to what the new space will be initialized to. It then frees the other malloc/calloc area as to not create a memory leak. However, while the most common thing to do is to set the same pointer that was pointing to the original malloc/calloc memory and assign it to the new reallocated space, you do not have to do this and can create a new pointer meaning that you will have an exact duplicate of the other malloc/calloc with a larger or smaller/truncated size meaning that the realloc can be used to copy in a sense a memory allocation. When you are done with the space it is up to you to free it as previously mentioned. It is important not to forget this, but more problems can occur if you try to free the same memory twice. The free() function takes a single parameter which is the pointer to the memory that you want to free which releases the memory so that it can be used in later memory allocations. If you try to free the same pointer twice an error will be thrown, however if you free a allocated piece of memory and then set the pointer to NULL/0 the free function will not throw an error and will do nothing and not break the code which is why in my code examples below I have the free function followed by assigning NULL or 0 to the pointer on the same line. For this exact reason.
#include <stdio.h> //printf()
#include <string.h> //memcpy
#include <limits.h> //INT_MAX
#include <stdlib.h> //rand(), srand()
#include <time.h> //time()

/* (*name_of_array)[] == how to dereference a pointer to an array*/
/* int *array[10] == an array of 10 int pointers*/
/* int (*array)[10] == dereferencing a pointer to an int which is stored at element 10 of the array "array".*/
/* int one = 1;*/
/* int two = 2;*/
/* int three = 3;*/
/* int four = 4;*/
/* int five = 5;*/
/* int* my_array[5] = {&one, &two, &three, &four, &five};*/
/* printf("%d", (*my_array)[3]); //prints the value 4 to the screen*/

#define ARRAY_SIZE 30

//malloc,calloc,realloc, dynamic memory has the scope of the pointer variable that points to it, but has the lifetime unlike any other, it is neither automatic/stack or part of the stack, but belongs to its own heap/dynamic/allocated memory space. Objects created here exist from the time they are created, until they are freed using the free() function. This can span multiple blocks and functions, or could happen 3 lines later if a free() function is used. However, the pointer that points to the block of memory is of whatever scope and lifetime it was created for and if it is lost/dies/goes out of scope this is a memory leak as their is no way to free the memory now and it will just keep taking up space that is no longer usable for future calls on heap memory (memory leak)
int main(void)
{
	srand(time(NULL));

	int static_array[ARRAY_SIZE];

	//fill static array with random numbers 0-9
	for (int i = 0; i < ARRAY_SIZE; ++i) {
			static_array[i] =  rand() % 9;
	}

	//print array
	for (int i = 0; i < ARRAY_SIZE; ++i) {
			printf("%d ", static_array[i]);
	}

	printf("\n\n");

	//dynamically create another array of twice the size and then copy over the contents
	// casts are not needed, but are often used to help other readers know whats going on, void pointer will be converted to whatever type the variable that is assigneed to the malloc is anyways even without cast
	/* int* heap_array = (int*) calloc(2, ARRAY_SIZE * sizeof(int)); //same as below but regardless of implementation it initializes all of the space to 0 */
     	if ( int* heap_array = (int*) malloc(ARRAY_SIZE * 2 * sizeof(int)) == NULL) {
     		fprintf(stderr, "Memory allocation failed\n");
     		exit(-1);
     	}



	memcpy(heap_array, static_array, ARRAY_SIZE * sizeof(int));

	//print out the new dynamically allocated array with the contents copied over
	for (int i = 0; i < ARRAY_SIZE * 2; ++i) {
			printf("%d ", heap_array[i]);
	}

	printf("\n\n");

        //I don't actually need it to be twice the size so truncate it to the original size + 3
	//realloc does not change the size of th ptr you pass to it, but rather finds a block of memory of the size you want it to be and then copies over all the contents as if they were always stored in that size and frees the previous block of memory
	// if realloc is smaller than original than it truncates, and if it is larger the values of the positions in the array past the ones defined before are undefined (on my mac using clang it always initializes everything to 0 though (implementation defined))
	// often times you assign it to the same ptr you are changine the size of but technically you can just assign it to another ptr and then both the original and the new block will exist and both need to be freed in order to not have memory leaks, this is uncommon though but I guess is one way to copy memory from one to another, but is mroe cumbersome and confusing for others reading your code
	if ( int* reallocated_array = realloc(heap_array, ARRAY_SIZE * sizeof(int) + (3 * sizeof(int))) == NULL) {
     		fprintf(stderr, "Memory re-allocation failed\n");
		//old malloc is still find, this just doesnt create the new space if it fails, old malloc remains untouched
	}

	//attempt to print it out as if I did not truncate it with realloc
	for (int i = 0; i < ARRAY_SIZE * 2; ++i) { //Implementation defined behavior going past array could all be initialized to 0, could segfault for going out of bounds, or could just return garbage
			printf("%d ", reallocated_array[i]);
	}

		printf("\n");

	free(reallocated_array); heap_array = NULL;
	free(heap_array); heap_array = NULL;
	free(heap_array); heap_array = NULL; //this line is just for educational purposes


	return 0;
}

File Operations
fopen() options: r w a, r+ w+ a+, rb wb ab, rb+ ab+ wb+:
Each of those options determines 5 different things you can do with the file you are opening.
b stands for binary which means that the file it will be dealing with will be read and/or written using binary meaning that when you use and read numbers you are not reading 0-9 as in ASCII 48-57 '0' - '9', but rather are reading ASCII 0 - 9. The purpose of binary files is that they can be used to store large structures on disk rather than memory, IO with binary files is faster because there is no conversion to binary required as it is already in binary. However binary can be more tricky because you need to know how to read the ones and zeros and determine which ones belong in a group and which ones do not. Binary files are unique to text files in another two distinct ways being that they are really built on the idea that they are made up of structures and each structure can essentially be accessed similar to how array elements can be accessed and indexing +1 skips to the next data structure in the file. This means that any structure in a file can be access from any other point in a file, though this whole concept is easier to understand in practice. This random access is a huge benefit.
When using the b option with the + option the + and the b can be in either order such as r+b or rb+ etc.
Note the standard refers to the + as the update option.


r: Open text file for reading. The stream is positioned at the beginning of the file.

r+: Open for reading and writing. The stream is positioned at the beginning of the file.

w: Truncate file to zero length or create text file for writing. The stream is positioned at the beginning of the file.

w+: Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file.

a: Open for writing. The file is created if it does not exist. The stream is positioned at the end of the file. Subsequent writes to the file will always end up at the then current end of file, irrespective of any intervening fseek(3) or similar.

a+: Open for reading and writing. The file is created if it does not exist. The stream is positioned at the end of the file. Subse- quent writes to the file will always end up at the then current end of file, irrespective of any intervening fseek(3) or similar.


FILE is the data structure that is returned by the fopen function, this is an abstract or opaque data structure that you are NOT SUPPOSED to know what it is as they are trying to abstract that away from you. The reason they do this is because how operating systems handle files differs drastically and thus using this opaque/abstract data type allows you to port this code much easier than if you realied on the structure and its specific makeup itself. Just note that for Unix it is usually implemented as a structure and in glibc there are literal warnings in the source code that says please do not use the structure directly and instead just refer to it via pointers which is just abstracting away from it even more.
The value stored in our FILE pointer and the value returned from fopen() function in the C standard library and the POSIX open() syscall is often reffered to as a file discrypter or file handler depending on whom you are talking about, this nomeneclature is reffering to the general data returned that we use inadvertently when we pass our FILE pointer to any of the soon to be mentioned file handling/operating functions that are provded to us.
This is why we use a FILE * variable in the assignment of fopen()
Files themselves are stored in storage/disk of the computer, but when we open it, we create a copy and store it in RAM/memory in what is reffered to as a buffer. A buffer is proxy to a file, changes to the buffer are NOT applied to the file directly until we finish operating on the buffer and it has time to save to the file. Most of the underlyings are handled by the host operating system and we do not have to worry much about them.
there are three special file discriptors on Unix platforms (Linux, BSD, MacOS), they are stdin, stdout, and stderr. stdin or Standard Input as its often reffered to is a buffer that takes input FROM a source, often the users keyboard, but could be from another process that writes to the stdin file discriptor, stdin stores this input in the buffer and waits until it is flushed or taken from another process or function. When you have used scanf() and then typed into the terminal when you are first learning to program in C, your input is first stored in stdin and then when you press the enter/return key (carriage return, newline, form feed) the input is flushed from the stdin buffer into the scanf function, which then probably stores it in some variable that you specified.
stdout is the name of the second important file discriptor which stands for Standard Output, this is by default the stuff that you print to the terminal/console/screen such as with the printf() function. Again it is stored in the buffer first and then is flushed to the printf() function and to the screen. This is why sometimes your printf() function calls do not always print immediatly when you want them to because there is a buffer holding it in the intermediary that has to be flushed before it prints, this is where sometimes you have to manually change the buffering which we will talk about shortly
stderr is the third and final of the special file discriptors and it is very similar to stdout, in that it prints to the terminal/console/screen by default. stderr stands for Standard Error and is used by developers/coders/programmers specifically for the purpose of storing error information, ther reason it is seperated from stdout even though by default they both print to the terminal/console/screen is so that if you wanted to redirect just one or the other so that they do not both clutter your screen this is much easier to do, on unix you can pipe and redirect output, error, and input away from the default areas.
input is by default the users keyboard, but can be redirected so that it takes the output of another program/process/thread/function and uses its output as stdin, in most shells that is what the | is doing, it is redirecting stdout into the stdin of something. The > operator in bash and many shells is used to pipe stdout to a file specifically rather than to another process/program. > is the same as &1>
stdout is as already mentioned piped to the terminal screen by default but this can be changed to be piped into something else using the | to pipe, or > to write or the >> to append to a file
stderr is reffered to similarly as the second file discriptor and can change where it outputs to via &2> or have it append somewhere using &2>>
stdin can be redirected into a process using the < operator on most linux systems (all of these will be covered in more detail in my bash guide whenever I make that)
As mentioned earlier that input and output is buffered before it is actually sent to the function that then reads the data and uses it in your program. This may seem like a nuisance, like why cant it just be fed directly into the program without a middle man you might ask? Well, the reason for this is actually quite complicated, but generally comes down to it actually improves performace and prevents data loss.
To remove buffering you can use either the setbuf() function or the setvbuf() function, both of which must be called before you actually use the buffer you want to modify. setbuf takes two arguments the first being the stream that you want to modify its buffering and the second being the size you want to buffer, honestly I have personally only gotten turning off buffering with this function by putting a 0 or NULL as the second argument to work. I have not got the function to do anything else. setvbuf() function on the other hand takes four arguments/paramters as compared to setbuf()'s two. The first is the file stream for the stream you want to modify its buffer, the second is a pointer to a character string or null to be used as the new buffer, the third is a special argument which takes one of three arguments _IOFBF, _IOLBF and _IONBF which stands for full buffering, line buffering and no buffering respectfully, the final argument is a size argument which is a backup for if the second argument passed in is NULL/0 then this argument will change the current buffering of this file stream to the size specified here rather then change the actual buffer that the file stream uses.
Finally the fflush() function is used to flush/clear a stream. This is often used with printf to force it to print to terminal even if it has not reached its desired buffer length in order to trigger it to do so
The next file operation I want to talk about is moving around the file, just like when you are writing in a file on your computer in every other way, there is a concept of a cursor-like object in C which is where you are currently at in the file you are reading and/or writing and when you read you read from the point of the cursor or write from it. However you can change where your currently at in the file as well as figure out where you are at in the file with a couple of different tools which we will discuss now that are all provided to us in the in the stdio.h header.
The tools for moving around the file are: fseek(), rewind(), fsetpos(), fgetpos()
fseek() is a function that you use to move the cursor around, it takes three arguments. The first argument is the stream in which you want to modify its cursor position. The second argument is how far away from the third argument you want to move the cursor to. The third argument is for one of three constants being SEEK_SET, SEEK_CUR, and SEEK_END. These three constants specify the very beginning of the stream, the current cursor location in the stream and the end of the stream. Thus the second argument is always relative to one of these three constants. So if we wanted to change the cursor to the 5th byte in a file we have to specify SEEK_SET in the third argument and 5 in the second argument which combined means "Five bytes past the beginning of the file". The function returns 0 if it was successful and a non-zero value if an error occurred in moving the cursor location. This function is used in conjunction with another function namely ftell(). These functions also have compatibility problems when on some OS newline characters are actually two characters rather than just one which messes with the ftell() and count of bytes.
rewind() is a function that specifically returns the cursor/current position of a file to the beginning and also clears the ferror() function. Honestly only if you really want to do both things should you use this function to make the code more clear and easier to understand, but honestly I would lean towards using fseek() with SEEKSET rather than using the rewind() function.
ftell() is a simple function that takes a single argument being the stream that you are working with and it simply returns the current position of the cursor in the file. This is used in conjunction with fseek() to move around the file. It should also be noted that there is a major downside to using these functions and constants to move around a file in that it becomes increasingly harder (near impossible??) to use some of these tools when working with files that are larger than 2GB in size. The reason for this being that ftell() returns a long value which can only store so much. fseek() with still work, but you can no longer use past the 2GB size the ftell function in conjunction and offset from it, you will have to have another way of keeping track of where the cursor is. If you are working with large files figure out how to work around these issues with these functions or the ones I am about to talk about. Another downside to the fseek and ftell functions is there lack of portability due to the fact that they are not great with wide characters and only recognize ASCII characters. This means they are not great for porting to other programs and machines that make great use of widechar, but for most simple programs this is not an issue.
fgetpos() and fsetpos() are functions that make use of the fpos_t type, the main benefit of these is that they can work with files greater than 2GB, however they suck about moving around the file as easily.
finally now that we have covered creating and moving around files and a lot of other important details about file operations it is now for the most important? fun? Interesting? Parts of file operations which is reading and writing your own data. This is pretty tricky in C and can be potentially dangerous. Please read other sources than mine as this website is just a hobby and should not be your only source for C knowledge and you should practice all of the following, preferably in a controlled in safe environment to see for your self how all of these tools work to prevent you from writing all over and destroying any of your data. Writing and reading files in C is no easy thing and I as someone who loves the C programming language still feel the need to warn you the C does not make this already difficult subject and dangerous operation that much easier for you. YOU HAVE BEEN WARNED!
lets first talk about reading from a file, the functions that pertain to reading from files in C are: fgets(), fscanf(), fgetc(), fread
EOF is a specific character in a file which refers to the END OF FILE, it is not an ASCII character and is represented using an int which is why some functions return an int rather than a char when they are dealing with characters, often times when reading until the end of a file you check if the character EOF has been found in a conditional statement, sometimes you will see the eof() function use in a loop, but this function should be used in caution because it does not tell you if the character is the EOF, but rather if the last character read previously was EOF. Thus eof() returns true (non-zero) if the EOF has been reached. Use this with caution and maybe just check each attempt to grab a character for success or failure and if they were EOF instead.
files for reading from a file or buffer are prefixed with an f, such as fscanf, fgets, fgetc(), fread
fgets(), fscanf(), fread() are the three most used to read from strings but all vary in multiple ways, firstly they can all be told to read up to a specific number of characters, but what they do with this information differs. These functions start reading from files from wherever the cursor is and thus are directly tied to the aforementioned functions and can be used together to reads specific parts of the files and not others. Another similarity being that when reading from a file all of these functions are reading from the current cursor position of the file and therefor moving around the file and then reading go hand in hand and are used together. fscanf() which is the only one where telling it how much to read is optional reads that many characters from the input source regardless if the number passed to it correctly corresponds to the number of characters that can be stored in the buffer/array passed to it. This means that it will keep reading information passed to it and overwrite the contents of the memory after the array is filled, this can lead to several problems including overwriting any variables that are stored after the array, stack overflow if you run out of available stack space with information that you write and segmentation faults meaning that you are writing to memory you do not have control over. All of these things can be minimized by prepending a number that indicates the size of the array/buffer that you are storing the string into between the % and the 's' such as %10s which will store 10 characters in the array that it points to but no more. However, here is where things must be taken into account, if you type in 11 characters two major problems can still happen. Firstly if your array is only 10 bytes long, there will be no room to add a null-terminator which is how all strings are stored in C and thus it will not be a string and trying to print the string can have major problems including the ones listed before since printf() has no way of knowing when the string ends. Secondly all of the text from the stream that scanf() is reading from will be left on the stream and thus further reads from this input stream will still have these characters waiting meaning they will be added first before any other input from the stream. Thus if you do scanf("%5s",foo); followed by another scanf("%6s", bar); and you type in "Clay is my first name" for the first scanf() the second scanf() will not ask the user for more input and will automatically be populated with "Otis S". Another thing to keep in mind using scanf() is that while a null terminated 0 will be added to the end of the string automatically by default, this will only be appended to the end of the string if there is room for the null terminator after the rest of the string. As scanf() fills the array without error checking and will not aid you if you fill up the space. scanf() also differs as it reads until a whitespace character if used with %s or until a character if using a scanset [^] however it will not store the newline character. Remember that everything that scanf() does not read is left in the stream and thus future calls will read those things first. fgets() does read and store newline characters and unlike fscanf() does not stop at white space. fgets() requires you to tell it how much to read and it will read one less than this and add a null terminator and keep the stuff it did not read in the stream.
all three functions leave whatever they do not read on the stream they are reading from, this means that any characters given to them and not read will inevitably be the first things stored when reading from that stream again in the future this is rarely ideal but there is no real good way to counteract this.
Imagine you are reading from stdin and for scanf() you do not want all of your carriage returns to be left in the stdin thus leaving subsequent calls will immediately read the newline character. In order to do this we can fflush(stdin); but this is undefined behavior although it does work with my clang compiler, your results may vary and this is not portable. Another thing you can do is have a while((c = getchar()) != '\n' && c != EOF), I know neither of these solutions are elegant, but the second option both works and is portable.
fscanf() allows input limits, but does not require it, both fgets() and fread() require max input. None of them enforce that the limit is actually less than or equal to the size of the buffer they are storing too and thus allow buffer overflows and stack overflows which can write over other data. scanf() and fgets() both append a null terminator to the string although fgets() will do so even if the number/limit passed into the function is less than or equal to size of the buffer. scanf() limit passed to the function must be at least one character less than the size of the buffer for it to add the null terrminator. Meaning that fgets() will replace the last character with a null terminator. fscanf() adds a null terminator by default but will not do so if the input and the size specified in the function call is greater than or equal to the size of the buffer. fread() does not append a null terminator and thus one needs to be added later. fgets() reads until the limit specified in the function call OR until it reaches a newline character or EOF character whichever comes first. It does store any whitespace and newline characters it comes across as long as they are read before its limit. fread() also stores any whitespace or newline character, but will continue past it until it reaches the limit specified in the function call. If the scanf is using %s it will read until: whitespace or a newline character is read, until the input length is equal to the optional length such as %10s will read up to 10 characters from the stream. fgets() and fread() are also useful as they read any text up to a set number of bytes or the conditions mentioned before, but scanf() must know ahead of time what the text it will be reading will be formatted like. scanf() is for FORMATTED text and thus if you want it to read letter and whitespace and everything you have to be very clear what you do and do not want it to read using format specifiers.
fgetc, getchar, getc,
While the functions mentioned above read any length of characters from a stream depending on your use case, there are a few more specialized tools such as fgetc(), getc(), and getchar(). getchar() and getc() are/or can be implemented as macros thus have the same limitations and strength as function-like macros have compared to normal functions. fgetc() is always a function. All three of these tools are used to grab single characters from a stream. getchar() differs from getc() in that it is a subset of getc() as it only takes input from stdin. Essentially getchar() is the equivalent to getc(stdin). Both grab a single character from the stream and then return it as a value, both of these functions return an int rather than a char which might seem weird at first, but this is because they also send the EOF character if they come across it which is not ASCII and thus needs an int in order to hold it. getchar() takes no arguments while getc() takes a single argument being the stream that you want to grab a character from. fgetc() is essentially equivalent to getc() in every way except that it is guaranteed to be implemented as a function and thus has all the benefits and cons of being a function while getc() might be a macro. This means that fgetc() might be slightly slower, but comes with a location in memory and thus can be passed by pointer into a function or array.
fputs, fprintf, fputc, fwrite
The difference between these functions is the same as that between fgets(), fscanf(), fread(), and fgetc(). These functions are used when dealing with files/streams. The first thing to note as that text in files and strings can only be modified in three ways: truncated (decreasing the size of the array they are in so that the characters no longer exist in usable memory), appended to the end of the string (assuming there is enough space to do so in the array), and overwriting the characters in the string with other characters. As you may have noticed there is no option explicitly to input text in the middle of other content. In reality what you have to do in this situation is have a second buffer that you store all of the text before the area you want to write to, then append your text to this area and then append the text that was after the destination you wanted to add text to. In this way all operations one can think of for text in a file can be handled. Yes I know this is clunky, but the more you do it the easier it gets and you will just get used to moving and appending strings to get them to say exactly what you want. fprintf() takes three arguments, the first being the stream in which you want to write the text to, the string you want to write, and any variables you want to be converted into the format string (like printf()). So this function is used when you have a specific format you want to follow. fputs() is for unformatted text to overwrite and/or append to a string. fwrite() is the equivalent to fread() in that it just prints the bytes to the stream and thus is good for using with binary files more so than the others. Where these write to the buffer is dependent on where the cursor is, and thus are closely related to the moving around the file functions mentioned earlier such as ftell(), fseek(), fpos_t, etc.
IOCTL, tmpfile, tmpnam, ferror
tmpfile() is a general functions in stdio.h that allows the user to create a temporary file that will be treated as such and will be removed when the computer powers off or is deleted manually. It takes no arguments and is used as the single rvalue in an assignment of a FILE pointer. Temporary files should still be close when no longer used to not create a memory leak in the program using the fclose() function. Another function that allows us to create temporary files in which we can refer to using there names is tmpnam() this function creates a unique name (or at least unique up to the constant TMP_MAX number of unique temporary files, with sizes of the names ranging up to the size L_tmpnam) this name is stored in a char pointer which can be used to create the file (as the tmpnam function only creates a unique name and not a file itself) this can be passed into the fopen() function as the name of the file for it to create and open a FILE pointer/file descriptor too.
ferror() is a function that returns non-zero if a problem occurs with reading, creating or writing to a file occurs and 0 if one doesn't. This function gets reset to 0 every time the rewind() function is called.
Honestly I DO NOT UNDERSTAND the ioctl() function, but it seems incredibly important. It has something to do with device drivers and files and how to actually read and get useful information from those files and thus be able to talk to the hardware of the computer including peripherals like mice, controllers, keyboards and other such devices, but my understanding in these areas of Unix and programming in general is so limited this function is not something I will probably cover in detail in much later BUT I REALLY WANT TOO! Simply because this function seems incredibly useful or important.
#include <stdio.h>
#include <stdlib.h>

#define ARR_SIZE 100

int main(void)
{
	char array[ARR_SIZE];
	while (1) {
		printf("Enter: ");

		fgets(array, ARR_SIZE, stdin); //safely reads up to 99 characters into buffer array
		//fgets(array, 101, stdin); //UNSAFE: might attempt to read and write past end of array

		fscanf(stdin, "%99[^\n]", array); //safely reads up to 99 characters into buffer array
		//fscanf(stdin, "%[^\n]", array); //UNSAFE: might attempt to read and write past end of array

		
		fread((void*) array, sizeof(char), ARR_SIZE - 1, stdin); 
                array[ARR_SIZE - 1] = '\0';  //safely reads into buffer and null terminates the string

		//fread((void*) array, sizeof(char), ARR_SIZE + 1, stdin); //UNSAFE CAN READ PAST AND ATTEMPT TO STORE PAST BUFFER
                // UNSAFE: unlike example above because this does not null-terminate the array of chars and thus is not a string

		printf("%s",array);
	}
	return 0;
}

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void)
{
	char* buffer = (char*) malloc(298172 * sizeof(char));
	char* buffer2 = (char*) malloc(298172 * sizeof(char));

	FILE *my_fptr = fopen("file_operations_test.txt", "r+");

	if (my_fptr == NULL) { 
		fprintf(stderr, "ERROR: Failed to find and open file.\n"); 
	} else { 
		fread((void*) buffer, sizeof(char), 298170, my_fptr); 
		char* location = strstr(buffer, "18"); 
		memcpy(buffer2, buffer, location - buffer); 
		strcat(buffer2, "2"); 
		strcat(buffer2, buffer + (location - buffer + 2)); 
		fseek(my_fptr,SEEK_SET, 0); 
		fprintf(my_fptr,"%s", buffer2); 
//		printf("buffer is: \n%s\n\nlocation of substring \"1\" is: %ld\n",buffer, location - buffer); 
	} 

	free(buffer); buffer = NULL; 
	fclose(my_fptr); 

	return 0; 
}
❌ Trigraphs and Digraphs (LEGACY)
Used when using a non-standard keyboard or operating system that does not provide access to certain keys. Usually this is a non-issue now a days as Unicode support is very common and they keys are no longer rarely used. It allows a person who does not have access to a certain symbol that is used in C programming to used the following two or three character sequences as a drop in replacement. The compiler may need help knowing this is what you are doing. For example in GCC you must use the -trigraphs flag in order to get these to compile.

??=      becomes     #
??(      becomes     [
??<      becomes     {
??/      becomes     \
??)      becomes     ]
??>      becomes     }
??’      becomes     ˆ
??!      becomes     |
??-      becomes     ∼
<%       becomes     {
%>       becomes     }
<:       becomes     [
:>       becomes     ]
%:       becomes     #
🔨 TODO: Things to write about, add to, or fix.
  1. Examples of functions, strings, bitwose operations, processes, threads, using other libraries (API's), multi-dimensional arrays and the math behind why you can ommit the []'s number in function paramater lists.
  2. Libraries, statically and dynamically linked (dlsym and function pointers)
  3. Debugging (why printf breaks code sometimes) (gdb/lldb, valgrind, objdump, etc.)
  4. Constant folding: (Compound literals, why "strings" are lvalues, why char* ptr = "Hello" " World"; creates a single string.)
  5. How compilers work in more detail
  6. Flexible array member, "struct hack"
  7. Alignment (alignas, alignof...)
  8. Opaque/abstract data types
  9. “Embedded c” (registers)
  10. size_t, ssize_t and %zu what do they really mean?
  11. Extensions including those by the compiler and __STDC_WANT_LIB_EXT1__
  12. Glibc vs Glib vs gnulib
  13. How floats, doubles and signed and unsigned numbers are stored in memory in more detail
  14. Sequence points (++i) * (++i) etc.
  15. (), "", and '' as punctuators/operators?
  16. Punctuators and how they differ from operators
  17. X Macros