C is a mid/low-level general purpose programming language preceding C++. C is relatively minimal compared to other languages like C++, it only has the features that it needs and no more which makes it technically much simpler as a language. The caveat of this is that some tasks require a lot more code from your part, for example C does not have any kind of dynamic arrays or strings, requiring you to use a library or make one yourself. C has been expanded since the creation of C++, and they are not 100% compatible anymore.

C has several versions with a rather confusing naming convention such as C99 and C11. The number refers to the year it was released, C99 being from 1999 thus older than C11 which is from 2011. The actual differences are very minimal and not relevant in many cases though.

Pros and Cons


  • Simple to learn compared to most other compiled languages
  • Very little abstraction, it gives you exactly what you write
  • Compiles into some of the fastest programs of all languages
  • Directly compatible with most operating systems, hardware, and many libraries
  • Encourages data-oriented thinking
  • Mostly compatible with C++
  • Supports in-line assembly
  • Writing code to read and write from binary files is trivial


  • While C is technically a simple language and easy to learn, it is hard to use effectively
  • Lacks a lot of modern/convenient functionality present in most other language (e.g. dynamic arrays), requiring you to write them yourself or use libraries, though some features are impossible to add properly (e.g. namespaces, type inferrence, methods..), making certain things clumsy to do
  • While C is still a common language today and many frameworks like SDL are compatible with C, it is more difficult to find tutorials or documentation to use those frameworks with it than another language


  • Static typing
  • Manual memory management; no garbage collection
  • No classes; plain-data structs only
  • No methods or constructors/destructors

Supported tools

Most operating systems and libraries can interface with C directly with no extra layers in-between, for example Windows API and OpenGL are C. However full game engines that support C are rare these days.








Sample code

Hello world

#include <stdio.h> // a library that provides access to printf()
void main(void) {
    printf("Hello, world!\n");


#include <stdio.h>
void main(void) {
    for( int x = 1; x <= 100; x++ ) {
        if(x%3 == 0 && x%5 == 0) {
        } else if(x%3 == 0) {
        } else if(x%5 == 0) {
        } else {
            printf("%d\n", x);

Using structs

#include <stdio.h>
typedef struct {
    float x;
    float y;
    float z;
} Vec3f;
typedef struct {
    Vec3f position;
    float speed_x;
    float speed_y;
    int inventory[32];
} Entity;
int entity_move (Entity* this, float gravity) {
    this->position.x += this->speed_x;
    this->position.y += this->speed_y;
    this->position.z -= gravity;
    return 1; // success
int main () {
    Entity thingy = { .speed_x = 1.f,  .speed_y = 1.f }; // this also initializes all other struct members to zero
    float gravity = 1.5f;
    // fill inventory with numbers
    for (int i=0; i<32; i++) {
        thingy.inventory[i] = i;
    while (1) {
        int moveresult = entity_move(&thingy, gravity);
        if (moveresult) {
            // movement success
            printf("thingy is at: x_%f y_%f z_%f\n", thingy.position.x, thingy.position.y, thingy.position.z);
            if (thingy.position.z < -1000.f) {
                // fell off the world
        } else {
            // movement failed! return error
            return 1;
    return 0; // returning 0 from main function indicates that the program finished properly. Returning 1 or another value indicates that there was a problem while running the program. Whoever started this program in the operating system then receives this number and can determine what happened.

Reading & Writing a binary file

#include <stdint.h>
#include <stdio.h> // fixed-length integer types
#pragma pack(push, 1) // Disable struct padding; always do that for structs you lazily serialize!
typedef struct {
    uint8_t checksum;
    uint8_t numberCount;
    uint32_t number[]; // C99 feature: Flexible Struct Member
} binaryFileStruct;
#pragma pack(pop) // Re-enabling struct padding for structs defined after this. (Very important if this is a header file!)
uint8_t calculateChecksum(const binaryFileStruct* data) { // calculate checksum
    size_t totalSize = sizeof(*data) + data->numberCount * sizeof(*data->number); // calculate size of binaryFileStruct
    const uint8_t* ptr = (const uint8_t*)data;
    ptr += sizeof(data->checksum);
    uint8_t checksum = 0;
    for(size_t i = sizeof(data->checksum); i<totalSize; i++) {
        checksum ^= *ptr; // We generate a checksum by simply xorring all the bytes together (skipping the checksum byte, of course.)
    return checksum;
void writeStruct(binaryFileStruct* data, const char* filename) { // This function writes the contents of binaryFileStruct to a file
    FILE* file = fopen(filename, "wb"); // Open the file in "w" write mode, "b" binary
    assert(file != NULL); // assert success
    size_t writeLen = sizeof(data) + data->numberCount * sizeof(*data->number); // calculate length of data to write to file
    size_t objectsWritten = fwrite(data, writeLen, 1, file); // write data to file
    assert(written == 1); // assert that we wrote one object (which has sizeof writeLen, as specified in fwrite)
binaryFileStruct* readStruct(const char* filename) { // This function read the content of a file and allocates a binaryFileStruct from it
    FILE* file = fopen(filename, "rb"); // Open the file in "r" read mode, "b" binary
    assert(file != NULL); // assert success
    binaryFileStruct header;
    size_t readLen = sizeof(header);
    size_t objectsRead = fread(&header, readLen, 1, file); // read data from file
    assert(read == 1); // assert that we read one object (which was sizeof readLen, as specified in fread)
    size_t flexibleSize = header.numberCount * sizeof(*header.number); // calculate size of remaining data
    binaryFileStruct* data = malloc(readLen + flexibleSize); // now that we know the size of the object, we can allocate memory for it
    assert(data != NULL); // assert success
    *data = header; // copy the data we have already loaded to allocated object
    objectsRead = fread(data->number, flexibleSize, 1, file); // read flexible data from file
    assert(read == 1); // assert that we read one object (which was sizeof flexibleSize, as specified in fread)
    assert(data->checksum == calculateChecksum(data)); // Verify data integrity using a checksum.
    return data;
void main(void) {
    binaryFileStruct* f = readStruct("testfile.dat"); // read file
    f->number[0] += 1; // Modify a number
    f->checksum = calculateChecksum(f); // update checksum
    writeStruct("testfile2.dat"); // write it back
    free(f); // cleanup
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License