├ /markup/ HTML • CSS • XML
├ /low_level/ Assembly • C • C++ • C# • Holy C • Rust
├ /high_level/ Java • Go • PHP • Python • SQL • Bash • JavaScript • PowerShell • ActionScript • Scratch • Ruby • Lua • P
└ /tutorials/ C • C++ • Java
Current mission: finish filling in the OTDs
SNCA:C

C is a general purpose programming language and one of the oldest and most influential. It is the ancestor language of C++ which in turn influenced C#, Java and Rust; it offers high control over memory with low overhead and is a very simple language (in terms of features), which is why it's used in software like Linux and DOOM (because Torvalds and Carmack are aryanGODs midwits who hate C++; definitely not because these projects are extremely old o algo). C does not use a garbage collector (unlike Java, C#, and Python) and all memory must be directly allocated and freed manually by using malloc() and free(). Even though C++ and Rust literally do this for free with no runtime cost by using smart pointers, RAII, and ownership-based memory. Also C is compiled ahead of time rather than interpreted or just-in-time compiled.
Terry Davis created Holy C which is his own dialect of C, which uses 'Just In Time' (JIT) compilation. It also borrows some features from C++.
C is the lingua franca of foreign function interfaces, meaning many languages have bindings to call C functions. For C++, because C++ preserves backwards compatibility with C, this is trivial.
History[edit | edit source]
C is one of the oldest languages that remain relevant today. It was created by Dennis Ritchie and Ken Thompson of Bell Labs in 1972, to construct utilities running on Unix. It later became popular, and standardised in 1989. Despite this, it remains an oldfag language that has updated and modernised very little since its inception, unlike its descendant C++ (and its descendants).
Sample programs[edit | edit source]
[+] Hello World
#include <stdio.h>
int main() {
printf("Hello world\n");
return 0;
}
[+] Memory issues
This demonstrates an out-of-bounds access, which causes a segmentation fault. Segmentation faults refer to illegal accessing of memory that a program does not have access to.
The "soy" string (in C, a string is char[] or array of char) is of length 6, and thus valid indices are 0 to 5. The for loop basically goes past that and starts reading other variables in memory. Some compilers don't like it, but it works on MSVC420 portable.
#include <stdio.h>
int main() {
char soy[] = "o algo";
for (int c = -1024; c < 1024; c++) {
printf("%c", soy[c]);
}
return 0;
}
This demonstrates a memory leak.
Here some memory is allocated, but never freed. An int[] is allocated with malloc() but free() is never called. If the pointer to that allocated memory is removed (such as popping off the call stack) then the block of memory essentially cannot be freed, causing a leak.
#include <stdlib.h>
void performUselessAllocation(size_t n) {
// allocation called
int* a = (int*)malloc(n * sizeof(int));
// the pointer is never returned so the memory cannot be freed,
// even by the caller
}
int main() {
performUselessAllocation(10);
// memory cannot be freed from here
}
[+] Tranny ACK
We first define the following header clamp.h:
#pragma once // ensures the header is included only once
#include <stddef.h>
#include <stdint.h>
// Alias float types acording to <stdint.h>
typedef float float32_t;
typedef long float64_t;
typedef long double float128_t;
// Define the bodies for both signed and unsigned type
#define CLAMP_BODY_U(type) \
type clamp_##type(type val, type min, type max) { \
return (val < min) ? min : (val > max) ? max: val; \
} \
\
unsigned type clamp_u##type(unsigned type val, unsigned type min, unsigned type max) { \
return (val < min) ? min : (val > max) ? max: val; \
}
// Define the body for only the signed type
#define CLAMP_BODY(type) \
type clamp_##type(type val, type min, type max) { \
return (val < min) ? min : (val > max) ? max: val; \
}
/**
* @brief A generic clamp function.
*
* @param val The value to clamp.
* @param min The minimum value.
* @param max The maximum value.
* @return The value after clamping.
*/
#define clamp(val, min, max) \
_Generic((val), \
char: clamp_char, \
unsigned char: clamp_uchar, \
int8_t: clamp_int8_t, \
uint8_t: clamp_uint8_t, \
int16_t: clamp_int16_t, \
uint16_t: clamp_uint16_t, \
int32_t: clamp_int32_t, \
uint32_t: clamp_uint32_t, \
int64_t: clamp_int64_t, \
uint64_t: clamp_uint64_t, \
float32_t: clamp_float32_t, \
float64_t: clamp_float64_t \
float128_t: clamp_float128_t \
size_t: clamp_size_t \
ptrdiff_t: clamp_ptrdiff_t
)(val, min, max)
CLAMP_BODY_U(char);
CLAMP_BODY_U(int8_t);
CLAMP_BODY_U(int16_t);
CLAMP_BODY_U(int32_t);
CLAMP_BODY_U(int64_t);
CLAMP_BODY(float32_t);
CLAMP_BODY(float64_t);
CLAMP_BODY(float128_t);
CLAMP_BODY(size_t);
CLAMP_BODY(ptrdiff_t);
Then in main.c:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "clamp.h"
/**
* @brief Determines whether the tranny should ack based on a given probability.
*
* @param ackProbability An (unsigned) integer between 0 and 100 representing the chance of acking.
* @return True if the random probability is less than the ackProbability; otherwise, False.
*/
bool shouldAck(unsigned int ackProbability) {
return (rand() % 101) < (float)clamp(ackProbability, 0, 100);
}
/**
* @brief Main function that decides whether to ack or scream TRANS RIGHTS ARE HUMAN RIGHTS!!!!!!!! based on probability.
*
* @param argc The number of command line arguments.
* @param argv An array of command line arguments.
* @return Whether the program executed successfully.
*/
int main(int argc, char *argv[]) {
srand((unsigned int)time(nullptr));
if (shouldAck(41)) {
fprintf(stderr, "%s\n", "ACK!!!");
raise(SIGSEGV);
} else {
printf("%s\n", "TRANS RIGHTS ARE HUMAN RIGHTS!!!");
}
return 0;
}
[+] Tranny ACK (Pre C23)
Before C23, you had to include <stdbool.h> because booleans were not a core primitive type in the language, also there was no nullptr so you had to use NULL.
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define clamp(val, min, max) (val < min) ? min : (val > max) ? max: val;
/**
* @brief Determines whether the tranny should ack based on a given probability.
*
* @param ackProbability An (unsigned) integer between 0 and 100 representing the chance of acking.
* @return True if the random probability is less than the ackProbability; otherwise, False.
*/
bool shouldAck(unsigned int ackProbability) {
return (rand() % 101) < (float)clamp(ackProbability, 0, 100);
}
/**
* @brief Main function that decides whether to ack or scream TRANS RIGHTS ARE HUMAN RIGHTS!!!!!!!! based on probability.
*
* @param argc The number of command line arguments.
* @param argv An array of command line arguments.
* @return Whether the program executed successfully.
*/
int main(int argc, char *argv[]) {
srand((unsigned int)time(NULL));
if (shouldAck(41)) {
fprintf(stderr, "%s\n", "ACK!!!");
// alternatively: fputs("ACK!!!\n", stderr);
raise(SIGSEGV);
} else {
printf("%s\n", "TRANS RIGHTS ARE HUMAN RIGHTS!!!");
// alternatively: puts("TRANS RIGHTS ARE HUMAN RIGHTS!!!");
}
return 0;
}
[+] Tranny ACK (Windows)
To compile make a nuproject under "Win32 Application" and choose Blank Application. in MSVC 4.2 portable, make a Main.c file in the project folder and paste it in then include it in the project, compile it and run it.
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>
#include "clamp.h"
/**
* @brief Determines whether the tranny should ack based on a given probability.
*
* @param ackProbability An (unsigned) integer between 0 and 100 representing the chance of acking.
* @return True if the random probability is less than the ackProbability; otherwise, False.
*/
bool shouldAck(unsigned int ackProbability) {
clamp(ackProbability, 0, 100);
return (rand() % 101) < (float)clamp(ackProbability, 0, 100);
}
/**
* @brief Main function that decides whether to ack or scream TRANS RIGHTS ARE HUMAN RIGHTS!!!!!!!! based on probability.
*
* @param hInstance Handle to the current instance of the application.
* @param hPrevInstance Handle to the previous instance of the application.
* @param lpCmdLine Command line arguments passed to the application.
* @param nCmdShow Specifies how the window is to be shown.
* @return Whether the program executed successfully.
*/
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
char message[256];
srand((unsigned int)time(nullptr));
if (shouldAck(41)) {
// Show "ACK!!!" message box
MessageBox(nullptr, "ACK!!!", "Result", MB_OK | MB_ICONINFORMATION);
const char msg[] = "ACK!!!";
WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), msg, strlen(msg), nullptr, nullptr);
} else {
// Show "TRANS RIGHTS ARE HUMAN RIGHTS!!!" message box
MessageBox(nullptr, "TRANS RIGHTS ARE HUMAN RIGHTS!!!", "Result", MB_OK | MB_ICONINFORMATION);
const char msg[] = "TRANS RIGHTS ARE HUMAN RIGHTS!!!";
WriteConsoleA(GetStdHandle(STD_OUTPUT_HANDLE), msg, strlen(msg), nullptr, nullptr);
}
return 0;
}
Tutorial[edit | edit source]
This is a brief tutorial on the C programming language.
Hello World[edit | edit source]
The Hello World program in C is as follows:
#include <stdio.h>
int main() {
printf("Hello, world!");
}
Here, #include <stdio.h> is a directive that tells the compiler to insert the contents of the standard library header <stdio.h> at the exact location of the #include directive. This header, <stdio.h>, contains various functions for input and output systems.
The main function int main() must always have a return type of int. However, an explicit return statement may be omitted in the main() function, which without a return statement implicitly returns 0 (indicates success). The main function must always reside in the global namespace.
Additionally, the signature of main may be int main(int argc, char* argv[]) to accommodate command-line arguments.
argcis the number of command line arguments, plus 1 for the program name.argvis an array ofchar*(charpointers, essentially the C way of representing strings), where the first element is the name of the executable and all subsequent elements are the command-line arguments.
Basic types[edit | edit source]
C consists of the following basic types:
int: an integer, usually 32 bitsshort: a smaller integer, usually 16 bitslong: a long integer, usually 64 bits. Note thatlong longis another long integer, which is guaranteed to have 64 bits, but is often not needed as in most systemslongis already 64 bits.float: a floating point (number with decimal), usually 32 bitsdouble: a floating point number, usually 64 bits. Note thatlong doubleis the analogous float fordouble, but is not always needed.char: a single ASCII character, with size of 8 bits.void: Represents no value (used in functions that don't return a value). This is also used invoid*(a void pointer), which will be explained later.
All integral types (int, char, long, etc.) have both a signed and unsigned version. By default, those types are signed, but if prepended with unsigned, they do not accommodate negative values, and have the minimum value of 0. If you exceed the range of allowed values in the type, it causes an overflow, which is dangerous in C, as unlike Python, there is a limit to the size of integers.
To declare strings, use the type char*. This is called a char pointer (where the * represents a "pointer" type). This will be explained in detail later. Note that you can't use == to compare strings, for reasons that will be explained later.
Variables[edit | edit source]
Variables in C work exactly the way you'd expect. You have to specify the type of the variable before declaring, obviously, but you do not re-declare a variable when reassigning its value.
#include <stdio.h>
int main() {
int age = 18;
char* name = "Nate";
char* website = "soyjak.party";
printf("%s is %d, and thus old enough to post on %s!\n", age, name, website);
}
In a string, \n represents a newline.
Reading input[edit | edit source]
To read input from the global input stream, use scanf(), which reads something from input and puts it into the variable.
#include <stdio.h>
int main() {
int a;
int b;
printf("Enter a first number: ");
scanf("%d", &a);
printf("\nEnter a second number: ");
scanf("%d", &b);
printf("\nThe sum of the numbers is a + b = %d + %d = %d\n", a, b, a + b);
}
Control flow[edit | edit source]
If/else[edit | edit source]
if, else if, and else allow you to check boolean conditions and execute code based on it. The basic syntax is if (condition1) { body1 } else if (condition2) { body2 } repeat else-if chains... else { conditionN }. You can have any number of else if blocks, even zero, and you can have just an if block by itself, but you cannot have an else or else if block by themselves.
#include <stdio.h>
int main() {
int number;
printf("Enter a number: ");
scanf("%d", &number);
printf("\n");
if (number > 0) {
printf("The number %d is positive.\n");
} else if (number < 0) {
printf("The number %d is negative.\n"
} else {
printf("The number is is exactly 0.\n");
}
}
Note that in older versions of C, the type bool (and its values true and false) were not directly part of the language, to use them you needed to include a header <stdbool.h>.
Switch[edit | edit source]
A switch block in C basically selects a path based on the value of an integral expression. This is often cleaner than using if/else checks (like YandereDev does), and when possible the compiler will expand it into a jump table, which is usually faster than if/else.
A switch block consists of cases to check against and an optional default case (if no case matches). In order to leave the switch block prematurely, you need to use a break; statement. If there is no break;, it continues on to the next case.
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("Monday");
break;
case 2:
printf("Tuesday");
break;
case 3:
printf("Wednesday");
break;
default:
printf("Some other day");
}
}
Loops[edit | edit source]
A loop in C is basically a way to repeatedly perform some code.
Loops support the two following keywords:
break: leaves the loop immediately.continue: ends the current iteration of the loop, and begins the next one immediately.
For loop[edit | edit source]
A for loop is a loop where you can control exactly how many times you want to iterate, and exactly when you want to, and how to change the loop as you go. The basic syntax is for (initialisation; condition; update) { body }.
#include <stdio.h>
int main() {
for (int i = 0; i < 5; ++i) {
printf("i = %d\n", i);
}
}
While loop[edit | edit source]
A while loop is basically a loop where you only control the condition. It is typically used when you don't know exactly how many iterations you want. The basic syntax is while (condition) { body }.
#include <stdio.h>
int main() {
int i = 0;
while (i < 5) {
printf("i = %d\n", i);
++i;
}
}
If you want an infinite loop, you would set the condition to just true, like while (true):
while (true) {
printf("Cobson will always be a gem!\n");
}
Do-while loop[edit | edit source]
A do-while loop is basically the same as a while loop, except it is guaranteed to execute once (even if the condition is false). The basic syntax is do { body } while (condition).
#include <stdio.h>
int main() {
int i = 0;
do {
printf("i = %d\n", i);
++i;
} while (i < 5);
}
Star pyramid[edit | edit source]
To make a star pyramid like in the Java tutorial, here is what you would do:
#include <stdio.h>
int main() {
int rows;
printf("Enter number of rows: ");
scanf("%d", &rows);
printf("\n");
for (int i = rows; i > 0; i -= 2) {
for (int j = 1; j <= i / 2; ++j) {
printf(" ");
}
for (int k = i; k <= rows; ++k) {
printf("*");
}
}
}
Functions[edit | edit source]
To create your own function, you begin with the return type, then the function name, then the list of parameters, and then the function's body.
void postBait() {
printf("Trans rights are human rights!");
}
int currentAdminNumber() {
return 6;
}
Pointers and arrays[edit | edit source]
In C, a pointer is a variable that stores the memory address of another variable.
int x = 10; int* addr = &x; // addr stores the address of x
For any type T, it has a corresponding type T*, meaning a pointer to that type T.
There are two important operators with pointers:
&, the address-of operator. This gets the address of where something lives in memory. For example,&xgets the address ofx.*, the dereference operator. This gets whatever is stored at the address ofp(yes I know this variable sounds like 'p but it stands for pointer, take your meds). For example,*pgets the value of whatever is stored at addressp.
nullptr (or NULL in older versions of C) represents a "null pointer", in other words a pointer that points to nothing, and usually has the memory address 0. It cannot be dereferenced, and if you dereference something that is a null pointer the program will crash.
int* p = nullptr; int x = &p; // Crashes the program!
This is a serious source of bugs in many C programs.
Arrays in C are a collection of objects stored in contiguous memory locations. They are indexed beginning with 0.
int numbers[5] = {1, 2, 3, 4, 5};
numbers[3] = 6;
// Now: numbers = [1, 2, 3, 6, 5]
// You don't always have to specify the length
int moreNumbers[] = {6, 7, 8, 9};
To loop through an array:
int numbers[] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; ++i) {
printf("numbers[i] = %d\n");
}
Unless you are using dynamic memory, arrays can't be resized and their size must be known at creation.
Arrays in C are closely related to pointers. For every type T, there is a type T[N], representing an array of T of length N. For example, when you declare int numbers[] = {1, 2, 3};, just using numbers itself acts like a pointer to the first element (numbers[0]). Also, if a function takes an array as a parameter, then it's essentially taking a pointer to the first element of that array.
The indexing operation is roughly equivalent to pointer arithmetic. For an array a: a[i] is equivalent to *(a + i), the element i entries away from a (the start).
Accessing an array beyond the valid range of indices causes the program to read whatever happens to be next in that point in memory, which is extremely unsafe and is a cause of many bugs.
You can also have a multi-dimensional array:
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
int value = matrix[0][2]; // value = 3
Void pointers[edit | edit source]
In C, there is a special kind of pointer, the void* (void pointer). Note that this doesn't mean it points to a void in memory (as void doesn't have a size and can't occupy memory), but rather is a pointer that can point to any data type, but does not know the type it is pointing to. You cannot directly dereference a void pointer, you must first cast it to something else. You also cannot perform pointer arithmetic on a void pointer.
void pointers are thus extremely useful in acting as generic code, as they can point to any piece of memory.
#include <stdio.h>
void printAsInt(void* data) {
printf("%d\n", *(int*)data);
}
int main() {
int x = 10;
void* p = &x;
printAsInt(p);
}
In the <stdlib.h> header, there are many useful algorithms which operate on void*, such as qsort(), which performs the quicksort algorithm on an array, or bsearch(), which performs the binary search algorithm on a sorted array.
Strings[edit | edit source]
In C, there isn't a native string type or class like there is in C++, Java, or any other language. Instead, a string is essentially represented as an array of chars, where the final element is the character '\0' (the null character), which has the ASCII value 0.
char s1[] = "Hello";
// s = ['h', 'e', 'l', 'l', 'o', '\0']
char s2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
// Declares an equivalent string using the explicit array syntax
Note that you cannot check s1 == s2, as this operation actually compares the memory addresses of s1 and s2. In order to check if strings are equal, you must use the strcmp() function from <string.h>.
#include <stdio.h>
#include <string.h>
int main() {
char s1[] = "Hello";
char s2[] = {'H', 'e', 'l', 'l', 'o', '\0'};
if (strcmp(s1, s2) == 0) {
printf("s1 and s2 are the same string");
} else {
printf("s1 and s2 are not the same string");
}
}
Note that both char[] and char* represent a string, but char* points only to a read-only string, whereas char[] can be modified.
Structs[edit | edit source]
In C, there are no classes like you might see in C++ or Java, but the closest thing is a struct, which aggregates data into a single object.
To access elements inside the struct, use the . syntax to get its elements.
struct Nusoi {
char name[50];
int years;
};
int main() {
struct Nusoi nate = {
.name = "Nate Higgers",
.years = 1
};
printf("%s is a nusoi who has been on the bald men with glasses website for %d year(s).", nate.name, nate.years);
}
If you have a pointer to a struct, instead of ., you need to use -> to get the fields:
struct Nusoi nate = {"Nate Higgers", 1};
struct Nusoi* nusoi = &nate;
printf("The nusoi %s has used the sharty for %d year(s)", nusoi->name, nusoi->years);
Enums[edit | edit source]
In C, an enum represents a type that assigns names some integer value.
enum Colour {
RED,
ORANGE,
YELLOW,
GREEN,
BLUE,
INDIGO,
VIOLET
};
Enums can also be manually assigned values.
enum Day {
MONDAY = 1,
TUESDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6,
SUNDAY = 7
};
Enums are particularly useful for representing a fixed set of related constants, especially if those values represent categories, states, or options. Note that in C, enums are not type-safe:
enum Colour colour = RED: enum Day day = MONDAY; day = colour; // day is assigned the integer value RED holds colour = 15; // colour is assigned the integer value 15, even if there's no Colour with value 15
In other words, enums are basically just ints with some named constants.
Dynamic memory allocation[edit | edit source]
In C, you can request direct access to memory using the <stdlib.h> header. This provides the following functions:
malloc(): allocates memorycalloc(): allocates memory and sets all bytes to0realloc(): re-allocates the memory, allowing you to grow or shrink the amount of allocated memory you havefree(): frees the memory allocated by returning it to the operating system
In C, memory is distinguished between the stack and the heap. In the stack, your allocation and deallocation is done automatically, and is much faster than the heap, but it must all be done at compile time and thus objects are fixed size. Meanwhile, on the heap, you have manual control over the memory and have far more space to work with, and can manually control the size, but are responsible for freeing the memory when you are done.
Such a problem with the stack is that if you don't know the size you need at compile time, you can use heap allocations to dynamically decide. With dynamic memory, you can also have strings of unspecified size, and grow the string as needed, rather than have a fixed limit size with stack-allocated char[]. This also allows you to create complex data structures that need to grow and shrink, such as linked lists, trees, graphs, hash tables, etc. Furthermore, if you need variables to persist even after a function ends, you can to use a pointer to heap memory and free it when you are done.
int* a = malloc(10 * sizeof(int)); // Creates space for 10 ints free(a); // Frees the memory in a
It is extremely important to free the memory once you are done with it. When the program is finished executing, the operating system will reclaim all the memory used by the program anyway, but if the program runs for too long it may continue to eat up memory without returning it to the operating system, causing an out of memory error.
If you lose the pointer you get from malloc() (without freeing it first), it causes a memory leak, and that memory is inaccessible forever. Languages with garbage collection, like Java, C#, and Python avoid this problem entirely, however, but are slower due to the overhead of garbage collection.
Furthermore, using a pointer after you have already freed it is a memory. You also cannot free a pointer twice.
Finally, malloc() may return nullptr if it fails to allocate, such as if you ask for too much memory. You must always check if malloc() returned nullptr after you call it.
Headers[edit | edit source]
In C, you typically split code into a header file (with extension .h), which contains the declarations, or "interface" of your library, while the actual code goes in the source file (with extension .c), which contains the actual definition (the implementation). To actually use your library, use the #include directive followed by the path to that header. Since including multiple files may cause the same file to be included twice (which causes a one-definition-rule violation), you have to add #pragma once to the top of the file to prevent a file from being included more than once.
For example:
In Nusoi.h:
#pragma once
struct Nusoi {
char* name;
int years;
};
struct Nusoi* newNusoi(char* name, int years);
struct Nusoi* deleteNusoi(struct Nusoi* nusoi);
char* getNusoiName(struct Nusoi* nusoi);
int getNusoiYears(struct Nusoi* nusoi);
void printNusoi(struct Nusoi* nusoi);
In Nusoi.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Nusoi.h"
struct Nusoi* newNusoi(char* name, int years) {
struct Nusoi* nusoi = malloc(sizeof(struct Nusoi));
// Note that you can check !nusoi instead of nusoi == nullptr
// since nullptr implicitly converts to false in boolean checks
if (!nusoi) {
// fprintf with first paramter as stderr writes to the
// standard error stream, instead of standard output stream
fprintf(stderr, "Failed to create nusoi!");
return nullptr;
}
strcpy(nusoi->name, name);
nusoi->years = years;
}
void deleteNusoi(struct Nusoi* nusoi) {
free(nusoi->name); // Free the name first
free(nusoi); // Then free the nusoi pointer
}
char* getNusoiName(struct Nusoi* nusoi) {
return nusoi->name;
}
int getNusoiYears(struct Nusoi* nusoi) {
return nusoi->years;
}
void printNusoi(struct Nusoi* nusoi) {
printf("Nusoi %s has been on the bald men with glasses site for %d year(s)", nusoi->name, nusoi->years);
}
Constants[edit | edit source]
A runtime constant is denoted with const, which makes a variable unmodifiable after it is initialised.
const int x = 10;
Meanwhile, a compile-time constant is denoted with constexpr. This constant must be initialised during compile-time, unlike const which can be initialised by something at runtime. It is a much stronger form of const, which the compiler will basically insert wherever it is used during compilation rather than store it in memory. Such constants are named in ALL_CAPS to emphasise that they are extremely important.
constexpr double PI = 3.14159265358979323846;
In older versions of C, this had to be done using a preprocessor directive, #define:
#define PI 3.14159265358979323846
However, this is considered far more unsafe, as the preprocessor performs textual substitution and can easily break code as it is not actually part of the compilation process.
Preprocessor[edit | edit source]
In C and C++, the steps of compilation:
- Preprocessing (expand all macros and preprocessor tokens, handle all file inclusions)
- Compilation (compile code to assembly)
- Assembling (assemble assembly code into machine code to object files)
- Linking (link translation units to be able to reference other object files, combine all object files into single binary)
So, before compilation is run, the compiler must first expand all macros. The C preprocessor is used to handle this step. The preprocessor itself does not understand the C language, but just modifies the text in place according to rules. It expands macros, includes header files (by copying the contents in), removes comments, and handles conditional compilation.
Common directives include:
#include: inserts contents of another file into code at that spot. Note the difference between angle brackets and quotations (#include <stdio.h>vs#include "Nusoi.h"): angle brackets are handled by first searching in the include directory of the project, while quotations are handled by searching relative paths.#embed: embeds binary at that spot. Basically#include, but for embedding binary information rather than code.#define: defines a macro. This can be either a constant, or a function-like macro. Function-like macros take a parameter and expand based on the parameter. For example:#define SQUARE(x) ((x) * (x)). Brackets must be used in preprocessor directives to ensure expressions are correctly parsed, as order of operations may break if it is not expanded within brackets.#undef: undefines a macro.#if/#elif/#else/#endif: allows code to be excluded depending based on certain conditions.#ifdef/#ifndef/#elifdef: allows code to be excluded based on whether a macro is defined.
#pragma: indicates compiler-specific instructions.#error: halts compilation if encountered.#warning: displays a compiler warning during compilation if encountered.#line: overrides the__LINE__and__FILE__macros
This is an example of using the preprocessor for conditional compilation:
#include <stdio.h>
#define DEBUG 1
int main() {
#if DEBUG
printf("Debug mode on\n");
#else
printf("Debug mode off\n");
#endif
}
Conditional compilation can be used to check the operating system, for example.
#include <stdio.h>
int main() {
#ifdef _WIN32
printf("GEEEEEG, do winjeets really?\n");
#elif __linux__
printf("GEEEEEG, do linuxtroons really?\n");
#elif __APPLE__
printf("GEEEEEG, do itoddlers really?\n");
#else
printf("I don't know what operating system is so I can't insult it o algo\n");
#endif
}
Safety concerns[edit | edit source]
(You) VVILL add images and make the text less dense.

Because C uses direct memory management, you have access to pointers which essentially point to something in memory (in either the stack or the heap).
- If you don't
free()memory on the heap that youmalloc()ed you will get a memory leak. - After you
free()memory, you cannot use whatever was there, as that memory no longer belongs to you; it has been returned to the OS. - Calling
free()on a pointer twice is illegal. Why? Because that memory might get used elsewhere, and the system doesn't know if you meant to free the first object or the object that might now live in that address. - A
void*(void pointer) is a pointer that can basically point to any sort of data type. The compiler doesn't know what it points to. You cannot dereference it directly, you must cast it to a specific type.- Because C doesn't have generics (you can make
_Genericmacros but that's a macro and not a function) you cannot write generic code like with C++ templates, you have to store things asvoid*and then cast them to the actual type. (This is similar to pre-Java 5 which did not have generics, so container types likeHashMapstored everything asObjectand had to be manually casted down to their actual type.) This is obviously not safe and also probably very inconvenient. (Also C doesn't allow operator overloading so you can't do things like define your own math structs and add them with +, a lot of fags will say operator overloading can be abused but same could be said about literally every other programming feature)
- Because C doesn't have generics (you can make
- If a pointer is null (
NULL) and you dereference it you will get a segmentation fault (segfault). (In Java dereferencingnullresults in aNullPointerExceptionwhich is like a controlled segfault on the JVM.) Also, a dangling pointer is a pointer that points to memory that has been deallocated, and accessing it is illegal.- C23 (the version of C released in 2023) adds
nullptrwhich is a type-safe null pointer constant. Prior to this C used a macroNULLwhich expanded to(void*)0which isn't heckin type safe o algo.
- C23 (the version of C released in 2023) adds
- Using an uninitialized variable is illegal.
- An integer overflow occurs when a calculation exceeds the maximum/minimum value of an integer.
- Accessing an index outside the length of an array causes a segmentation fault. Also, there is no native
Stringtype in C, there is onlychar[](arrays ofchar) orchar*(pointers to string literals). - A stack overflow is when the call stack exceeds the allocated size. This can happen when, for example, you allocate too many variables or have a function call itself recursively.
- A buffer overflow is when you write more data to a buffer than it can hold. A buffer might be an array, string, or some block of memory. This will corrupt memory. It is also used by hackers such as the sharty to perform exploits such as code injections and arbitrary code executions, such as changing the return address to call some other function.
- Undefined behavior is when you do something that is illegal and therefore not defined by the standard. When that happens there is no guarantee what happens - it is entirely up to compiler implementation. Undefined behavior is allowed by the C standard to allow compilers to make optimizations.
Rust is more aryan than C. People who claim C is more aryan are nocoder trannies who get all their opinions from /g/ and have never written C. A malding tranny wrote this sentence and the list above if that matters.
Gallery[edit | edit source]
-
C pointer and array syntax.
C is part of a series on Computing

>I wrote my own fucking compiler I'm not a nigger like Linus
➜ /languages
➜ /software
├ /imageboards/ nusoi • Vichan • Yotsuba • OpenYotsuba
├ /operating_systems/ Windows • Linux • Android • TempleOS • BSD
├ /applications/ Web Browser • Photoshop • Flash • MS Paint • IRC
├ /dev/ Free-software license • Game development
└ /misc/ Babybot • McChallenge • CAPTCHA • Systemd • RAID • Ricing • 4get • Snarkysnappydoxingtool.bat • JS Paint
➜ /cyb
➜ /ai
└ ChatGPT • Gemini • Grok • Vibe coding • Generative AI • Stable Diffusion