Programming in C: Building Your Own AI Assistant

Rafal JackiewiczRafal Jackiewicz
15 min read

Welcome to the wonderful world of C programming, where semicolons rule and memory leaks lurk in the shadows!

Have you ever wanted to build your own AI assistant that doesn't spy on you, sell your data, or randomly burst into flames? Well, put on your developer hat (preferably a fedora, for maximum debugging power) because today we're going to create "Jarvis" - a command-line AI assistant using pure C and the magic of TCP socket programming!

Why C Programming?

Because we hate ourselves, obviously! Just kidding (mostly). C gives us control that higher-level languages can only dream about. It's like driving a car with a manual transmission versus an automatic - sure, you might stall a few times and occasionally roll backward into expensive German sedans, but the feeling of control is worth it!

What Are TCP Sockets?

Think of TCP sockets as the postal service of the internet. They establish connections between applications across a network, ensuring reliable, ordered delivery of data. Unlike UDP (which is more like throwing messages into the wind and hoping they reach their destination), TCP makes sure your packets arrive intact and in the correct order.

In our case, we'll use sockets to communicate with OpenAI's servers so we can chat with their AI models. Let's get started!

Programming with libcurl in C

Before diving into our project, let's understand how to use libcurl properly. libcurl is a powerful client-side URL transfer library that handles the low-level socket operations for us. It's like hiring a professional driver instead of manually shifting gears yourself.

The libcurl Lifecycle

Working with libcurl follows a specific lifecycle:

  1. Global Initialization: Start by initializing the libcurl environment

  2. Handle Creation: Create a curl easy handle

  3. Option Setting: Configure the handle with your desired options

  4. Request Performance: Execute the request

  5. Cleanup: Free resources and shutdown the libcurl environment

Step 1: Global Initialization

Always begin by initializing the libcurl environment:

curl_global_init(CURL_GLOBAL_ALL);

This sets up necessary global resources that libcurl needs. The CURL_GLOBAL_ALL flag initializes everything, including SSL support and socket operations. You could use more specific flags like:

  • CURL_GLOBAL_SSL - Just initialize SSL

  • CURL_GLOBAL_WIN32 - Initialize Windows-specific features

  • CURL_GLOBAL_NOTHING - Explicitly initialize nothing

Step 2: Creating a Curl Handle

Next, create a curl easy handle:

CURL *curl = curl_easy_init();
if (!curl) {
    fprintf(stderr, "Failed to initialize curl\n");
    return 1;
}

This handle is your interface to all curl operations - think of it as your "connection manager."

Step 3: Setting Options

libcurl's power comes from its extensive option system. You configure your connection through these options:

// Set the URL
curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/endpoint");

// Set SSL verification (CRITICAL for security!)
curl_easy_setopt(curl, CURLOPT_CAINFO, "cacert.pem");
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

// Set headers
struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "Content-Type: application/json");
headers = curl_slist_append(headers, "Authorization: Bearer YOUR_TOKEN");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

// Set request type (GET is default)
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L); // For GET
// Or for POST:
curl_easy_setopt(curl, CURLOPT_POST, 1L);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, "your-post-data");

// Set timeouts
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); // 30 seconds timeout
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); // 10 seconds connect timeout

Step 4: Handling Response Data

Unlike simpler libraries, libcurl doesn't automatically store response data. You need to provide a callback function that processes the incoming data:

// Structure to store the response
struct MemoryStruct {
    char *memory;
    size_t size;
};

// Callback function to handle received data
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    // Reallocate memory to fit the new data
    char *ptr = realloc(mem->memory, mem->size + realsize + 1);
    if(!ptr) {
        fprintf(stderr, "Out of memory!\n");
        return 0; // Signal error to libcurl
    }

    // Store the new data
    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0; // Null terminate

    return realsize; // Return the size of processed data
}

// Set up the callback
struct MemoryStruct chunk;
chunk.memory = malloc(1);  // Initialize with a small size
chunk.size = 0;

curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

This callback is where the socket data is actually processed. libcurl receives data from the socket and passes it to your callback function.

Step 5: Performing the Request

Now execute the request:

CURLcode res = curl_easy_perform(curl);
if(res != CURLE_OK) {
    fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}

This is where all the TCP socket magic happens behind the scenes:

  1. libcurl opens a socket connection

  2. Performs the SSL/TLS handshake if using HTTPS

  3. Sends your HTTP headers and data

  4. Receives the response and passes it to your callback

  5. Manages error conditions and retries if needed

Step 6: Getting Response Information

You can extract useful information about the response:

// Get HTTP response code
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
printf("HTTP Response Code: %ld\n", http_code);

// Get content type
char *content_type = NULL;
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
printf("Content-Type: %s\n", content_type);

Step 7: Proper Cleanup

Always clean up to prevent memory leaks:

// Free the header list
curl_slist_free_all(headers);

// Clean up the curl handle
curl_easy_cleanup(curl);

// Global cleanup
curl_global_cleanup();

// Free your memory structure
free(chunk.memory);

Thread Safety Considerations

libcurl is thread-safe with some caveats:

  • curl_global_init() and curl_global_cleanup() are not thread-safe

  • Each thread should use its own curl handle

  • Never share handles between threads without proper locking

Debug Mode for Troubleshooting

When troubleshooting, enable debug mode to see what's happening under the hood:

// Set debug function
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_debug_callback);

// Debug callback function
static int my_debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) {
    // Print different types of debug info
    switch(type) {
        case CURLINFO_TEXT:
            fprintf(stderr, "* %s", data);
            break;
        case CURLINFO_HEADER_OUT:
            fprintf(stderr, "=> Send header: %.*s", (int)size, data);
            break;
        case CURLINFO_DATA_OUT:
            fprintf(stderr, "=> Send data: %zu bytes\n", size);
            break;
        // And so on for other info types...
    }
    return 0;
}

Now let's apply these principles to our AI assistant project!

Project Overview: Building Jarvis

Our goal is to create a simple C program that:

  1. Takes a question as input

  2. Connects to OpenAI's API using TCP sockets (via libcurl)

  3. Sends our question to the API

  4. Gets a response from the AI

  5. Extracts and displays the response

Prerequisites

Before we begin our journey into the C programming abyss, you'll need:

  1. A C compiler (GCC, Clang, MSVC)

  2. CMake for building

  3. libcurl for handling HTTP requests

  4. cJSON for parsing JSON

  5. An OpenAI API key

  6. CA certificate bundle (cacert.pem) for SSL verification

For the CA certificate bundle, you'll need to download cacert.pem from curl.se/docs/caextract.html. This file contains trusted root certificates needed for secure HTTPS connections. Without it, your program won't be able to verify the SSL certificate of the OpenAI API, and you'll either get connection errors or be vulnerable to man-in-the-middle attacks - neither of which is particularly fun!

Step 1: Setting Up the Project Structure

First, let's set up our project. Create a directory for our project and the following files:

jarvis/
├── main.c
├── cJSON.c
├── cJSON.h
├── CMakeLists.txt
├── jarvis.conf
└── cacert.pem (for SSL verification)

You can download cJSON from GitHub and libcurl from curl.se for Windows or using your package manager on Linux.

Make sure to place the cacert.pem file in your project directory. This is crucial for establishing secure connections - think of it as your program's "list of trusted friends" on the internet.

Step 2: The Configuration File

Let's start with something simple - our configuration file. Create a file named jarvis.conf:

OPENAI_API_KEY=sk-proj-...
MODEL=gpt-4o-mini
DEBUG=true

Replace the API key with your own. This is where we'll store our OpenAI API key and model preference. Setting DEBUG to true will help us see what's happening behind the scenes - like watching the sausage get made, but for HTTP requests!

Step 3: Setting Up CMake

Let's create our CMakeLists.txt file. This is like a recipe for building our program:

For Windows:

cmake_minimum_required(VERSION 3.22)
project(jarvis C)

set(CMAKE_C_STANDARD 17)

# Curl Paths
set(CURL_INCLUDE_DIR "D:/Libraries/curl-8.13.0_2-win64-mingw/include")
set(CURL_LIB_DIR "D:/Libraries/curl-8.13.0_2-win64-mingw/lib")

# Include directories
include_directories(${CURL_INCLUDE_DIR})

# Library directories
link_directories(${CURL_LIB_DIR})

# Add cJSON source files
add_library(cjson STATIC cJSON.c)

add_executable(jarvis main.c)

# When using MinGW curl on Windows, the library name is typically libcurl.dll.a or libcurl.a
target_link_libraries(jarvis
        libcurl  # or might be libcurl.a or just curl
        cjson
        )

For Linux:

cmake_minimum_required(VERSION 3.22)
project(jarvis C)

set(CMAKE_C_STANDARD 17)

# Find libcurl package
find_package(CURL REQUIRED)

# Include directories
include_directories(${CURL_INCLUDE_DIRS})

# Add cJSON source files
add_library(cjson STATIC cJSON.c)

add_executable(jarvis main.c)

# Link libraries
target_link_libraries(jarvis
        ${CURL_LIBRARIES}
        cjson
        )

On Linux, we use find_package() instead of setting paths manually because we're civilized people who use package managers!

Step 4: The Main Code

Now for the fun part - the actual C code! Let's break down the main components:

Headers and Definitions

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>
#include "cJSON.h"

#define CONFIG_FILE "jarvis.conf"

// Structure to hold response data
struct MemoryStruct {
    char *memory;
    size_t size;
};

// Global debug flag
int debug_enabled = 0;

Here we're including necessary headers and defining a structure to store our HTTP response. We're using a global debug flag because we like to live dangerously! (Actually, it's for convenience, but don't tell anyone).

Helper Functions

Next, let's implement some helper functions:

// Function to extract and print the assistant's response from the JSON
void extract_and_print_content(const char *json_string) {
    cJSON *root = cJSON_Parse(json_string);
    if (!root) {
        fprintf(stderr, "Error parsing JSON: %s\n", cJSON_GetErrorPtr());
        return;
    }

    // Navigate through the JSON structure
    cJSON *choices = cJSON_GetObjectItem(root, "choices");
    if (choices && cJSON_IsArray(choices) && cJSON_GetArraySize(choices) > 0) {
        cJSON *first_choice = cJSON_GetArrayItem(choices, 0);
        if (first_choice) {
            cJSON *message = cJSON_GetObjectItem(first_choice, "message");
            if (message) {
                cJSON *content = cJSON_GetObjectItem(message, "content");
                if (content && cJSON_IsString(content)) {
                    // Print just the content
                    printf("\n%s\n", content->valuestring);
                } else {
                    fprintf(stderr, "Content not found or not a string\n");
                }
            } else {
                fprintf(stderr, "Message not found\n");
            }
        } else {
            fprintf(stderr, "First choice not found\n");
        }
    } else {
        fprintf(stderr, "Choices not found or empty\n");
    }

    // Clean up
    cJSON_Delete(root);
}

// Parse boolean value from string
int parse_bool(const char* value) {
    if (!value) return 0;

    if (strcmp(value, "1") == 0 ||
        strcasecmp(value, "true") == 0) {
        return 1;
    }
    return 0;
}

char* read_config(const char* key) {
    static char line[512];
    FILE* file = fopen(CONFIG_FILE, "r");
    if (!file) {
        perror("Config file");
        exit(1);
    }

    while (fgets(line, sizeof(line), file)) {
        line[strcspn(line, "\r\n")] = 0;
        char* eq = strchr(line, '=');
        if (!eq) continue;
        *eq = '\0';
        char* value = eq + 1;

        if (strcmp(line, key) == 0) {
            // Special handling for DEBUG property
            if (strcmp(key, "DEBUG") == 0) {
                debug_enabled = parse_bool(value);
                fclose(file);
                return _strdup(value);
            }
            fclose(file);
            return _strdup(value);
        }
    }
    fclose(file);
    fprintf(stderr, "Error: Key '%s' not found in %s\n", key, CONFIG_FILE);
    exit(1);
}

// Function to escape JSON string
char* escape_json_string(const char* input) {
    if (!input) return NULL;

    size_t input_len = strlen(input);
    // Allocate enough memory for worst case scenario (every char needs escaping)
    char* result = (char*)malloc(input_len * 2 + 1);
    if (!result) return NULL;

    size_t j = 0;
    for (size_t i = 0; i < input_len; i++) {
        switch (input[i]) {
            case '\"':
                result[j++] = '\\';
                result[j++] = '\"';
                break;
            case '\\':
                result[j++] = '\\';
                result[j++] = '\\';
                break;
            case '\b':
                result[j++] = '\\';
                result[j++] = 'b';
                break;
            case '\f':
                result[j++] = '\\';
                result[j++] = 'f';
                break;
            case '\n':
                result[j++] = '\\';
                result[j++] = 'n';
                break;
            case '\r':
                result[j++] = '\\';
                result[j++] = 'r';
                break;
            case '\t':
                result[j++] = '\\';
                result[j++] = 't';
                break;
            default:
                result[j++] = input[i];
        }
    }
    result[j] = '\0';
    return result;
}

The escape_json_string function escapes special characters in our input string so it plays nicely with JSON. Without this, a single backslash or quote mark could cause our JSON to explode in a spectacular fashion - the programming equivalent of adding water to a deep fryer!

libcurl Callback Functions

Now let's add our callback functions for libcurl:

// Callback function for writing received data
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
    size_t realsize = size * nmemb;
    struct MemoryStruct *mem = (struct MemoryStruct *)userp;

    char *ptr = realloc(mem->memory, mem->size + realsize + 1);
    if(!ptr) {
        fprintf(stderr, "Not enough memory (realloc returned NULL)\n");
        return 0;
    }

    mem->memory = ptr;
    memcpy(&(mem->memory[mem->size]), contents, realsize);
    mem->size += realsize;
    mem->memory[mem->size] = 0;

    return realsize;
}

// Debug function to print libcurl verbose information
static int my_trace(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) {
    // Skip printing if debug is not enabled
    if (!debug_enabled) return 0;

    (void)handle; /* prevent compiler warning */
    (void)userp;

    const char *text;
    switch(type) {
        case CURLINFO_TEXT:
            fprintf(stderr, "* %s", data);
            break;
        case CURLINFO_HEADER_OUT:
            text = "=> Send header";
            fprintf(stderr, "%s\n%.*s\n", text, (int)size, data);
            break;
        case CURLINFO_DATA_OUT:
            text = "=> Send data";
            fprintf(stderr, "%s (%zu bytes)\n", text, size);
            break;
        case CURLINFO_SSL_DATA_OUT:
            text = "=> Send SSL data";
            fprintf(stderr, "%s (%zu bytes)\n", text, size);
            break;
        case CURLINFO_HEADER_IN:
            text = "<= Recv header";
            fprintf(stderr, "%s\n%.*s\n", text, (int)size, data);
            break;
        case CURLINFO_DATA_IN:
            text = "<= Recv data";
            fprintf(stderr, "%s (%zu bytes)\n", text, size);
            break;
        case CURLINFO_SSL_DATA_IN:
            text = "<= Recv SSL data";
            fprintf(stderr, "%s (%zu bytes)\n", text, size);
            break;
        default: /* in case a new one is introduced to shock us */
            return 0;
    }
    return 0;
}

The WriteMemoryCallback function is where the magic of socket programming happens behind the scenes. When data comes in through our TCP connection, libcurl calls this function to handle the incoming bytes. We're just making sure we have enough memory to store it all - like preparing a big enough bucket to catch rainwater.

The Main Function

Finally, the main function that ties everything together:

int main(int argc, char* argv[]) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s \"your question\"\n", argv[0]);
        return 1;
    }

    CURL *curl;
    CURLcode res;
    const char* question = argv[1];
    char* api_key = NULL;
    char* model = NULL;
    char* debug_setting = NULL;
    char* escaped_question = NULL;
    struct MemoryStruct chunk;
    struct curl_slist *headers = NULL;
    int ret_val = 1;

    // Initialize memory chunk
    chunk.memory = malloc(1);
    chunk.size = 0;

    // Read configuration
    api_key = read_config("OPENAI_API_KEY");
    model = read_config("MODEL");
    debug_setting = read_config("DEBUG");  // Read DEBUG setting

    // Escape the question for JSON
    escaped_question = escape_json_string(question);
    if (!escaped_question) {
        fprintf(stderr, "Failed to escape the question string\n");
        goto cleanup;
    }

    // Initialize curl
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (!curl) {
        fprintf(stderr, "Failed to initialize curl\n");
        goto cleanup;
    }

    curl_easy_setopt(curl, CURLOPT_CAINFO, "cacert.pem");

    // Set the URL for the POST request
    curl_easy_setopt(curl, CURLOPT_URL, "https://api.openai.com/v1/chat/completions");

    // Set up the auth header
    char auth_header[256];
    snprintf(auth_header, sizeof(auth_header), "Authorization: Bearer %s", api_key);
    headers = curl_slist_append(headers, auth_header);
    headers = curl_slist_append(headers, "Content-Type: application/json");
    headers = curl_slist_append(headers, "User-Agent: jarvis-c-client/1.0");
    curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

    // Build the JSON payload
    char* json_payload = NULL;
    size_t json_len = 0;
    json_len = snprintf(NULL, 0,
                        "{\"model\":\"%s\",\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]}",
                        model, escaped_question) + 1;

    json_payload = (char*)malloc(json_len);
    if (!json_payload) {
        fprintf(stderr, "Failed to allocate memory for JSON payload\n");
        goto cleanup;
    }

    snprintf(json_payload, json_len,
             "{\"model\":\"%s\",\"messages\":[{\"role\":\"user\",\"content\":\"%s\"}]}",
             model, escaped_question);

    // Set the POST fields
    curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_payload);

    // Set up the write callback function
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunk);

    // Set timeout
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 120L);

    // Only set verbose mode if debugging is enabled
    if (debug_enabled) {
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, my_trace);
    }

    // Set SSL verification options
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

    // Only print if debug is enabled
    if (debug_enabled) {
        printf("Sending request to OpenAI API...\n");
    }

    // Perform the request
    res = curl_easy_perform(curl);

    // Check for errors
    if(res != CURLE_OK) {
        fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
        goto cleanup;
    } else {
        // Get HTTP response code
        long http_code = 0;
        curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

        if (debug_enabled) {
            printf("HTTP Response Code: %ld\n", http_code);
        }

        // Print the response
        if (debug_enabled) {
            printf("\n--- Server Response ---\n");
        }

        if (chunk.size > 0) {
            if (debug_enabled) {
                printf("%s\n", chunk.memory);
            }
            extract_and_print_content(chunk.memory);

            if (debug_enabled) {
                printf("Total received: %zu bytes\n", chunk.size);
            }
        } else {
            printf("No data received from server.\n");
        }

        if (debug_enabled) {
            printf("--- End of Response ---\n");
        }
    }

    ret_val = 0;

    cleanup:
    if (debug_enabled) {
        printf("Cleaning up...\n");
    }

    if (escaped_question) free(escaped_question);
    if (api_key) free(api_key);
    if (model) free(model);
    if (debug_setting) free(debug_setting);
    if (chunk.memory) free(chunk.memory);
    if (json_payload) free(json_payload);
    if (headers) curl_slist_free_all(headers);
    if (curl) curl_easy_cleanup(curl);
    curl_global_cleanup();

    return ret_val;
}

Pay special attention to this line:

curl_easy_setopt(curl, CURLOPT_CAINFO, "cacert.pem");

This tells libcurl where to find the SSL certificate authority bundle, which is essential for verifying the identity of the server we're connecting to. Without this, either your connection will fail, or worse, you might connect to an imposter server! It's like checking ID before letting someone into your house.

Socket Programming Under the Hood

Now, you might be thinking, "Wait, where's the socket programming? I don't see any socket(), connect(), or recv() calls!"

Well, my eager apprentice, libcurl abstracts away the raw socket operations for us. Under the hood, it's doing all the dirty work:

  1. Creating a socket with socket()

  2. Connecting to the server with connect()

  3. Sending data with send()

  4. Receiving data with recv()

  5. Handling SSL/TLS encryption

  6. Managing HTTP headers and response codes

If we were true masochists, we could implement all of this ourselves, but even C programmers have limits to their self-punishment!

Building and Running the Project

On Windows:

mkdir build
cd build
cmake .. -G "MinGW Makefiles"
mingw32-make

On Linux:

mkdir build
cd build
cmake ..
make

Now you can run your new AI assistant:

./jarvis "What's the meaning of life?"

And Jarvis will respond with whatever philosophical musings the AI has to offer!

Common Issues and How to Fix Them

1. "No SSL Support in libcurl" Error

This means your libcurl was compiled without SSL support. You need this to connect to HTTPS URLs. Make sure you download a version with SSL support or recompile with SSL enabled.

2. SSL Certificate Verification Failed

If you see errors about SSL certificate verification, make sure:

  • The cacert.pem file is in the same directory as your executable

  • The file path in CURLOPT_CAINFO is correct

  • The certificate bundle is up-to-date (certificates do expire!)

You can always download the latest bundle from curl.se/docs/caextract.html.

3. Memory Leaks

If your program starts acting like it has dementia (or Windows starts complaining about memory), check that you're freeing all allocated memory. C doesn't hold your hand with garbage collection - it expects you to clean up your own mess, like a responsible adult.

4. JSON Parsing Failures

If you get JSON parsing errors, check that your input is properly escaped. A single unescaped quote can turn your beautiful JSON into digital spaghetti.

Taking It Further

Now that you've built a basic AI assistant, here are some ways to enhance it:

  1. Add a conversation history to maintain context

  2. Implement streaming responses for real-time feedback

  3. Create a simple GUI (if you're feeling particularly masochistic)

  4. Add voice input/output capabilities

  5. Optimize memory usage for larger responses

Conclusion

Congratulations! You've just built your own AI assistant using C and TCP socket programming (via libcurl). While higher-level languages might let you accomplish this in fewer lines of code, nothing beats the satisfaction of building something from the ground up in C.

Remember, C programming is like riding a bicycle - except the bicycle is on fire, you're on fire, everything is on fire, and you're in hell. But once you get the hang of it, it's an incredibly rewarding skill that gives you deep insight into how computers actually work.

Now go forth and conquer the world with your new C programming powers! Just remember to free your memory, or your computer will hate you forever.

Happy coding!


Author Bio

Rafal Jackiewicz is an author of books about programming in C, C++ and Java. You can find more information about him and his work on Amazon.


P.S. If your computer explodes while running this code, you probably missed a semicolon somewhere. It's ALWAYS a semicolon.

0
Subscribe to my newsletter

Read articles from Rafal Jackiewicz directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Rafal Jackiewicz
Rafal Jackiewicz

Rafal Jackiewicz is an author of books about programming in C and Java. You can find more information about him and his work on https://www.jackiewicz.org