C
About
-
Bill Sky - The Computer Guy (Playlist) .
-
Haven't seen it yet, but I think I like the guy.
-
Beginning
-
-
Starts with the basics, like
int main() { return 0; }.
-
Tips
-
-
C Versions.
-
He uses C99.
-
-
Compiler Flags.
-
Unity Build.
-
Debugger.
-
Recurring segfault problems can only really be debugged with a debugger. It's very hard to debug a segfault with prints, and the segfault error given is usually terrible and not descriptive.
-
-
ASan.
-
Address Sanitizer.
-
Accessing memory out of scope causes a Segfault, but accessing memory within the scope of something else in the code can cause memory corruption.
-
For this reason, ASan is used to check if access is within the array bounds, etc.
-
-
Arrays and Strings.
-
He creates his own arrays and strings using structs, so he can implement his own "boundary checks", etc.
-
-
Indexes and Pointers.
-
Suggestion: store the index of an object instead of a pointer to the object.
-
Apparently, this helps with array resizing, etc.
-
-
Arenas.
-
Related to Lifetime.
-
"Arena = One Lifetime".
-
It's where you store all data that has the same lifetime.
-
-
Includes
-
Unity Build.
-
A way to handle
#includesand avoid duplicate includes. -
I found it interesting; I saw it being used in Zig too.
-
-
#pragma oncein.hfiles.
Libraries
-
-
A helper library by Ginger Bill, to group everything together.
-
(2025-07-03) On a quick glance, I found it very useful, as it resembles Odin types.
-
-
stb .
-
Single-file public domain libraries for C/C++.
-
Created by Sean Barrett.
-
~ Advice for Writing Small Programs in C .
-
Quotes :
-
The C std library is terrible.
-
Some APIs are not designed very well.
-
strcopyis notoriously bad.
-
-
I needed a way to systematically reuse code.
-
JAI is quite interesting.
-
He worked on Thief , creating the render engine.
-
Thief is known for having good programming; use of 'fat structs'.
-
It was the first time ECS was used; the entity is just an ID for a lookup table.
-
-
OOP is a waste of time.
-
Spend your time writing useful code, not doing other things. Aka, minimize overhead.
-
Many libraries are written only for big projects.
-
He disagrees with Casey on some things, but agrees quite a bit on others.
-
-
Overall, the video is mostly irrelevant; the only "relevant" things are the quotes I put.
-
-
Critiques
-
Ginger Bill: "C11 is the last version of C that I will use. All other versions after that are not C anymore."
Note
-
The comments below were taken from A Reply to Let's Stop Copying C - Ginger Bill .
-
I agree with all the points below.
Type first
-
C’s “type first” was a weird design decision. It was meant to express that the type declaration is the same as how it is used in an expression.
-
For this reason, C has given us some of the most “wonderful” declarations:
int *x[3]; // array 3 of pointer to int
int (*x)[3]; // pointer to array 3 of int
_Atomic unsigned long long int const volatile *restrict foo[]; // Yeah...
-
And not to mention that type qualifiers can be in different places and still mean the same thing:
int const x;
const int x;
const int *y;
int const *y;
-
C’s approach also requires a symbol table while parsing to disambiguate between type declarations and an expression e.g.:
foo * bar; // Is this an expression or a declaration? You need to check the symbol table to find out
Weak typing
-
C’s implicit numeric conversions and array demotion to pointer conversion are a source of many bugs and confusions.
-
C++ tried to fix some of this but still its type system is quite weak in many regards.
typedef int My_Int;
int x = 123;
My_Int y = x; // this is allowed as `My_Int` is just an alias of `int`
Strings
-
C’s null-terminated strings made sense back in the day but not anymore.
-
Storing the length with the pointer to the data is personally my preferred approach, as this allows for substrings very easily.
-
#include
-
Back in the day when C was made, it is perfectly understandable why
#includewas used over different approaches. C was designed to be implementable with a single-pass compiler. -
However, since the advent of more modern computers, this approach is not needed and a proper library/module/package system is a better solution.
-
C++ approach with
namespaces is not a solution and more of a hack.
switch
-
Default fallthrough is painful.
-
Use break in every case.
-
Increment and Decrement
-
++and--are expressions which allow for things like*ptr++andarray[index++]to be done. -
However, in a language that does not require these “hacks”, these operators are quite useless .
-
Another issue is that order of evaluation for
++is undefined behavior in C, soarray[index++] = ++index;is not a good idea.
Single returns and out parameters
-
yeah.
Silent Errors
-
To develop.
Bitwise operator precedence
-
C’s operator precedence for bitwise operations is bad and requires an “overuse” of parentheses.
C Standard
-
A specification.
-
“Standard C” = features defined in the ISO C specification.
-
It never provides code for the library. The goal of the standard is portability of behavior, not uniformity of implementation.
-
It defines two things:
-
The language rules :
-
syntax, semantics, types, etc.
-
-
The required library interface :
-
headers, functions, macros, their specified behavior
-
-
-
C runs on everything from microcontrollers to supercomputers.
-
Implementations need hardware-specific optimizations (e.g., using vector instructions or memory-aligned stores).
-
The standard only mandates what must happen, not how.
-
The C language defines a portable abstract machine .
-
The C standard library defines a portable API to that abstract machine.
-
What’s “standard” is not what a machine supports, but what all conforming C environments must make available.
-
Real hardware differs wildly—word size, endianness, memory model, I/O systems.
-
C’s purpose is to let code compile and behave the same way across all that.
-
The C standard library is small compared to the standard libraries of some other languages. The C library provides a basic set of mathematical functions, string manipulation, type conversions, and file and console-based I/O. It does not include a standard set of "container types" like the C++ Standard Template Library, let alone the complete graphical user interface (GUI) toolkits, networking tools, and profusion of other functionality that Java and the .NET Framework provide as standard. The main advantage of the small standard library is that providing a working ISO C environment is much easier than it is with other languages, and consequently porting C to a new platform is comparatively easy.
Libraries from the C Standard specification
Headers
-
Anything not listed there (e.g.
<conio.h>,<unistd.h>,<pthread.h>, filesystems, sockets, threads, graphics) is implementation-specific, not covered by the C standard; it is intentionally left to external or platform libraries (POSIX, Win32, etc.). -
“Implementation extensions” = compiler or OS additions beyond it.
Why these topics?
-
This topics were chosen to define a minimal and portable core, enough to write complete programs on any system without assuming specific hardware or OS features.
-
The C committee’s logic:
-
Include only functionality that every environment can realistically support (memory manipulation, text handling, math, time).
-
Exclude features that depend on an operating system, filesystem layout, or hardware model (threads, graphics, networking).
-
Keep the language lightweight so it can run on bare metal as well as general-purpose systems.
-
The aim was universality, not completeness.
-
Opinions
Good
-
It provides a portable contract : if you stick to standard headers and functions (e.g., from The C Programming Language or the ISO/IEC spec), you get behavior that works across many platforms.
-
It keeps the “core” relatively small (at least compared to many modern high-level libraries), so you get low-level control, minimal runtime overhead.
-
Because implementation is deferred (you don’t get code in the spec), libraries can be optimized per hardware/OS, which makes C usable in embedded, OS, and systems-programming domains.
Problems
Buffer overflow vulnerabilities
-
Some functions in the C standard library have been notorious for having buffer overflow vulnerabilities and generally encouraging buggy programming ever since their adoption.
-
The most criticized items are:
-
string-manipulation routines, including
strcpy()andstrcat(), for lack of bounds checking and possible buffer overflows if the bounds are not checked manually; -
string routines in general, for side-effects "Side effect (computer science)"), encouraging irresponsible buffer usage, not always guaranteeing valid null-terminated output, linear length calculation;
-
printf()family of routines, for spoiling the execution stack when the format string does not match the arguments given. This fundamental flaw created an entire class of attacks: format string attacks; -
gets()andscanf()family of I/O routines, for lack of (either any or easy) input length checking.
-
-
Except the extreme case with
gets(), all the security vulnerabilities can be avoided by introducing auxiliary code to perform memory management, bounds checking, input checking, etc. This is often done in the form of wrappers that make standard library functions safer and easier to use. -
C11 seems to improve on that a bit.
Threading problems, vulnerability to race conditions
The
strerror()
routine is criticized for being thread unsafe and otherwise vulnerable to race conditions.
Error handling
-
The error handling of the functions in the C standard library is not consistent and sometimes confusing. According to the Linux manual page
math_error, "The current (version 2.8) situation under glibc is messy. Most (but not all) functions raise exceptions on errors. Some also seterrno. A few functions seterrno, but do not raise an exception. A very few functions do neither.
Critiques
-
Some researchers question how standards are set: choices may be influenced by consensus, legacy, politics, rather than purely technical optimality.
-
Because C supports so many platforms (embedded, low-resource, large servers), the library must compromise between extremes — meaning “what you get” may not be ideal for your domain (e.g., game dev, safety critical, high-perf).
Versions
C89/C90
-
Original ANSI/ISO C standard.
C99
-
Added
inline,//comments, variable-length arrays, and more standard headers.
C11
-
Introduced multithreading (
<threads.h>),_Atomic, and optional bounds checking. -
C11 .
C17
-
Bug-fix revision of C11, no major new features.
C23
-
Adds UTF-8 literals, improved enums, and more modern conveniences.
C Standard Library (libc)
-
Implementation of the C Standard.
-
Each one implements the C Standard optimized for the target platform.
Most Popular Implementations
-
BSD libc
-
Various implementations distributed with BSD-derived operating systems.
-
-
GNU C Library (glibc)
-
Used in GNU Hurd, GNU/kFreeBSD, and most Linux distributions.
-
-
Microsoft C run-time library (MSVCRT / UCRT).
-
Part of Microsoft Visual C++. There are two versions of the library: the formerly-redistributable (until Visual Studio 2013) MSVCRT which is not compliant to the C99 standard, and the newer UCRT (Universal C Run Time) shipped as part of Windows 10 and 11 which is C99-compliant.
-
-
dietlibc
-
An alternative small implementation of the C standard library (MMU-less)
-
-
ÎĽClibc
-
AC standard library for embedded ÎĽClinux systems (MMU-less)
-
uclibc-ng
-
An embedded C library, fork of ÎĽClibc, still maintained, with memory management unit (MMU) support
-
-
-
Newlib
-
A C standard library for embedded systems (MMU-less)6 and used in the Cygwin GNU distribution for Windows
-
-
klibc
-
Primarily for booting Linux systems
-
-
musl
-
Another lightweight C standard library implementation for Linux systems
-
-
Bionic
-
Originally developed by Google for the Android embedded system operating system, derived from BSD libc
-
-
picolibc
-
Developed by Keith Packard, targeting small embedded systems with limited RAM, based on code from Newlib and AVR Libc.
-
What am I using?
-
If you just wrote that code and compiled it normally with a mainstream compiler, here’s what you can expect:
-
Linux (gcc or clang) :
-
uses glibc’s by default.
gcc main.c -o main # links with glibc automatically -
-
macOS (clang)
-
Uses Apple’s libc (based on FreeBSD’s) implementation.
-
-
Windows (MSVC)
-
Uses Microsoft’s Universal CRT (UCRT) implementation.
-
-
-
It’s always the platform’s standard C library implementation, automatically linked when you compile with
gcc,clang, orMSVC.
Finding out which implementation was used
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int *nums = malloc(sizeof(int) * 5);
memset(nums, 0, sizeof(int) * 5);
return 0;
}
-
Just looking at this code above is not possible to know which implementation was used.
-
To find out, you can check:
-
Which C library your program links to
-
ldd ./a.outshows dynamic libraries. If you seelibc.so.6you’re using glibc (or its symlink). If you seemuslorlibc.sofrom another vendor that tells you the vendor.
-
-
Which
memsetsymbol is present in that libc-
objdump -T /lib/x86_64-linux-gnu/libc.so.6 | grep memsetornm -D /lib/x86_64-linux-gnu/libc.so.6 | grep memset -
This shows whether
memsetis provided by that library.
-
-
Does your binary actually call the libc function or was it optimized/ inlined?
-
Inspect the binary symbols:
readelf -Ws ./a.out | grep memset-
If you see an
UNDentry formemsetit’s an external call.
-
-
Disassemble the binary:
objdump -d ./a.out | lessand search forcall.*memset.-
If there is no call, compile-time optimization may have inlined a loop or emitted an intrinsic.
-
-
To see compiler codegen: compile to assembly:
-
gcc -O2 test.c -S -o test.s(optimized) andgcc -O0 test.c -S -o test_O0.s(unoptimized). Inspect for acalltomemsetor a loop/rep stosbsequence.
-
-
-
Building
Memory
malloc
-
malloc(size) -
C89.
-
Allocates
sizebytes and returns a pointer to the allocated memory. The memory is not initialized . Ifsizeis 0, thenmalloc()returns a unique pointer value that can later be successfully passed tofree(). -
For glibc:
-
By default
mallocuses the program break (sbrk/brk) for ordinary allocations and switches tommapfor large allocations. -
There is a tunable
MMAP_THRESHOLD(initially ~128KB) that controls whenmmapis used for an allocation; this can change dynamically and is adjustable withmallopt. Aallocations below that threshold normally do not triggermmap.
-
Example
-
malloc(sizeof(int))(4 bytes)-
rounded up to alignment/size-class → typically 8 or 16 bytes.
-
allocator finds a free block in the 8B/16B size-class run and returns a pointer to the payload.
-
metadata: either a small header immediately before the payload (ptmalloc-style) or a bitmap/side-table recording that this slot is used (jemalloc-style).
-
-
malloc(sizeof(uint64_t))(8 bytes)-
rounded to same 8B/16B class as above; may sit next to the
intif the allocator gave contiguous small blocks. -
allocation cost is O(1) if a free block exists in that class.
-
-
malloc(100)(string/array)-
rounded up to the nearest size class (e.g. 112 or 128 bytes depending on class table).
-
allocator picks a block from the corresponding run; if the run has no free blocks it may split a larger free block or request more runs/pages from the chunk (or request a new chunk via
mmapif the chunk is exhausted).
-
-
malloc(1 << 20)(1 MiB — large example)-
this may be larger than the allocator’s “small” classes and will likely be satisfied by allocating a span of pages (one or more pages) from the chunk; if it’s larger than allocator thresholds, the allocator may
mmapa separate region dedicated to this allocation.
-
Other Allocators
calloc
-
calloc(count, size) -
C89.
-
Allocates space for
countelements each ofsizebytes and zero-initializes the entire block. -
The
calloc()function allocates memory for an array ofnelements of size bytes each and returns a pointer to the allocated memory. The memory is set to zero. Ifnor size is0, thencalloc()returns a unique pointer value that can later be successfully passed tofree(). -
If the multiplication of
nandsizewould result in integer overflow, thencalloc()returns an error. By contrast, an integer overflow would not be detected in the following call tomalloc(), with the result that an incorrectly sized block of memory would be allocated:
realloc
-
realloc() -
The
realloc()function changes the size of the memory block pointed to byptosizebytes. The contents of the memory will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. -
If
pisNULL, then the call is equivalent tomalloc(size), for all values ofsize. -
If
sizeis equal to zero, andpis not NULL, then the call is equivalent tofree(p). -
Unless
pisNULL, it must have been returned by an earlier call tomallocor related functions. If the area pointed to was moved, afree(p)is done.
jemmalloc
-
jemmaloc -
Typically obtain large fixed-size chunks from the OS (4 MiB is a common default chunk size on 64-bit builds) and then carve many small allocations out of those chunks. That’s why a bunch of 1-byte allocations won’t generate many
mmapsyscalls. -
Focuses on low fragmentation, predictable behavior, per-arena concurrency, and strong profiling/monitoring features. Widely used in servers and language runtimes.
tcmalloc
-
tcmalloc(Google) -
Thread-caching allocator optimized for low-lock fast allocation on many threads (per-thread caches + central free lists). Good throughput in highly concurrent workloads.
mimalloc
-
mimalloc(Microsoft) -
Compact, high-performance general-purpose allocator that emphasizes speed and low overhead; simple drop-in use and some novel design choices for cross-thread frees.
Mem Set
-
Example .
-
“The
memsetfunction copies the value of(unsigned char)cinto each of the firstnbytes of the object pointed to bys.”
void *memset(void *s, int c, size_t n) {
unsigned char *p = s;
unsigned char value = (unsigned char)c;
for (size_t i = 0; i < n; i++) {
p[i] = value;
}
return s;
}
-
This snippet is effectively what you’d write to satisfy the standard in the simplest way possible. It’s not from glibc, musl, BSD, or any other real system library.
-
Real-world
memsetimplementations almost never look like that—they are highly optimized in assembly, e.g.:-
glibc (x86) uses
rep stosband alignment tricks. -
musl uses word-sized writes in tight loops, with SIMD paths.
-
newlib and BSD libc have similar optimizations tuned per architecture.
-
Mem Copy
memcopy
-
memcpy(void *dest, const void *src, size_t n) -
Copies
nbytes fromsrctodest. Behavior is undefined if source and destination overlap. Typically faster because it assumes no overlap and may use wider loads/stores or hardware instructions.
memmove
-
memmove(void *dest, const void *src, size_t n) -
Copies
nbytes, but guarantees correctness when source and destination overlap. It does this by checking pointer order and copying in the correct direction (forward or backward) to prevent overwriting unread data. -
In summary, same thing that
memcopy, but handles overlap.
Mem Zero
-
void bzero(void *s, size_t n); -
Is a legacy C library function that sets a block of memory to zero.
-
Equivalent to:
-
memset(s, 0, n);
Loops
int main() {
for (int i = 0; i < 5; i++) {
printf("%d\n", i);
}
return 0;
}
-
There is no "iterator pattern" in C.
-
This is not possible in C:
for car in cars
Strings
-
char *
Structs
struct City c = {
.name = "San Francisco".
.lat = 37,
.lon = -122,
};
Pointers
Imo
-
The concept is nice and intuitive.
-
The syntax is abysmal, terrible.
Explanations
-
-
He goes fast during the explanation of pointers, but has some interesting comments.
-
Syntax
-
Declaration:
int y = 2; int *pX = &y; or int y = 2; int* pX = &y;-
This implies a pointer is being declared.
-
"The 'Int Pointer' pX is equal to the address of y."
-
"pX is a pointer that points to the address of y, where y is of type int. This implies that the 'dereference' of pX is y."
-
-
Expression:
a = my_function(*pX);-
This implies using the dereference of pX as a parameter for the function; i.e., the value the pointer points to.
-
Operators ( . / -> / * )
-
Operators are 'ways to access properties', e.g.,
house.window = 2,var vector = Vector2.UP. -
Normally, just use
.to access a property, but with pointers,->may be necessary.-
Learning C++ from GDScript {23:40 -> 25:55} .
-
Good explanation about this.
-
Syntax is terrible and confusing, tho.
-
-
Arrays
-
dmitsuki:
-
All an array is, is if you have a starting address 0, and an int is 4 bytes wide, if you index into the array for the second position, start at the first position and go 4 bytes (4 * 1)
-
To get the third position it would be 4 * 2, etc. That's all an "array" is: a pointer, and then you do math to its address
-
-
Caio:
-
That sounds so weird, it seems like I'm just scrolling through memory without much consideration for typing or any boundary checking. Idk, maybe this is the way it is in the end, but sounds odd to me at first.
-
So you can happily store beyond the bounds of an array because, to the CPU, it's just another pointer address. Bounds checking and such is enforced at a higher level (i.e., the language that generated the instructions)
-
-
Lee:
-
Rest assured it's not intuitive to anyone. If things like this were intuitive, the industry and humanity would look very different.
-
-
Barinzaya:
-
That's C in a nutshell
-
It is what's actually happening at the CPU level. The CPU doesn't know about arrays, structures, etc. It just deals in "load a value from/store a value to this address". It doesn't know nor care about the larger structure that that value may be a part of.
-
-
dmitsuki:
-
That's why people made new languages with new features
-
But fundamentally that is what is happening
-
It's also why certain security problems exist, you can read past the bounds of an array and read memory you shouldn't
-
Preprocessors
Undefined Behavior
GameDev
-
Anima Well.
-
-
The video is nice, no big deal, but okay to watch.
-
Basically talking about the development experience, nothing particularly useful is mentioned, just interesting to see the challenges, etc.
-
Basically nothing about the game's design or art is discussed, just programming challenges, etc.
-
-
The person is a solo-dev.
-
C++, not C.
-
Visual Studio.
-
Almost everything is handmade; all platform layers seem to be done manually.
-
WIndows C API
-
The Windows API (WinAPI) is the low-level programming interface of the Windows operating system. Its main purpose is to allow programs to interact directly with OS resources.
-
Create and manage windows
-
Manipulate files and directories
-
Manage processes and threads
-
Access hardware and devices
-
Manage memory
-
Network communication
-
Graphical interface (GDI)
-
-
The Windows API in C sounds terribly complex, confusing, unintuitive, and very outdated.
-
ChatGPT: "It’s common among programmers that the Windows API in C (WinAPI) is confusing, verbose, and old-fashioned."
-
Casey: He agrees that the API is bad.
-
"it's crazy, I'm never going to defend the design of the Windows API".
-
He doesn’t like using Windows.
-
"It’s crazy how new APIs and Windows updates make everything increasingly ridiculous."
-
-
Extensive Use of Pointers and Opaque Types
-
Many types are just
void*aliases, likeHANDLE,LPARAM,WPARAM, making it difficult to understand what is actually being handled.
-
-
Excessive Macros and Definitions
-
The API uses macros excessively (
#definefor constants and window messages), making the code hard to navigate and understand.
-
-
Archaic and Inconsistent Naming
-
Functions start with prefixes like
Create,Get,Set, but don’t always follow an intuitive pattern. -
Example:
CreateWindowExdoes not directly create a window, just registers it.
-
-
Use of C-Style Strings and Verbose Structures
-
Many functions require manually filling huge structs (e.g.,
WNDCLASSEXto create a window).
-
-
Explicit Resource Management
-
The programmer must manually manage memory allocations, handlers, and system objects, risking leaks if forgotten.
-
-
Confusing and Historical Documentation
-
Many functions have legacy versions from Windows 3.1/95 (
CreateWindow,CreateWindowEx, etc.), adding complexity when deciding which to use.
-
-
Opaque Error Messages
-
To understand an error, often you must call
GetLastError()and then useFormatMessage()to make it readable.
-
-
-
Is WinAPI still used today?
-
Yes, but less directly. Today, most developers use frameworks that encapsulate WinAPI, like:
-
C++ with Qt, wxWidgets, or WinRT
-
C# with .NET and WPF
-
Rust with
winapi-rs -
Electron (JavaScript) for cross-platform apps
-
-
If you need low-level development on Windows (like drivers, emulators, or embedded software), WinAPI is still the official route.
-
-
Do you need WinAPI for games?
-
No, if you are using frameworks like SDL, RayLib, SFML, GLFW, or engines like Unity/Unreal.
-
Frameworks like SDL, RayLib, SFML, GLFW, and even engines like Unity and Unreal remove almost all need to interact directly with WinAPI. They act as an abstraction layer, letting the developer focus on the game itself.
-
-
Yes, if you want to build a graphics engine from scratch or integrate specific Windows features.
-