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
Pros
- 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
Cons
- 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
Properties
- 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.
Libraries
Frameworks
Engines
Resources
Tutorials
- Tutorialspoint
- Handmade Hero - A very long series of making a game from scratch with no libraries, using mostly C.
- Intro to C - 5 episodes preceding Handmade Hero.
IDEs
Compilers
Sample code
Hello world
#include <stdio.h> // a library that provides access to printf() void main(void) { printf("Hello, world!\n"); }
FizzBuzz
#include <stdio.h> void main(void) { for( int x = 1; x <= 100; x++ ) { if(x%3 == 0 && x%5 == 0) { printf("fizzbuzz\n"); } else if(x%3 == 0) { printf("fizz\n"); } else if(x%5 == 0) { printf("buzz\n"); } else { printf("%d\n", x); } printf("\n"); } }
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 break; } } 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.) ptr++; } 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) fclose(file); } 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 }