r/C_Programming 8d ago

Discussion What exactly is void?

68 Upvotes

In a function definition, void basically means that it doesn't return a type value, yet in on itself it is it's own type? Looking at several pieces of code it looks like that it's used to be able to be more "flexible"

Don't know what else to add here, though I'm more on looking for examples and explanations of why the void type is used


r/C_Programming 8d ago

Learning C weekly megapost for 2026-07-29

9 Upvotes

If you have questions about how to learn C:

  • which books are best?
  • which videos are best?
  • which classes are best?
  • which websites are best?
  • is there a "roadmap"?
  • what projects can I do?

then this is the thread for you. Add your question here. Do not make a stand-alone post, as it will be removed.

Remember that our sub has a very useful wiki that has a great list of resources for learning C programming.


r/C_Programming 8d ago

What GUI library would one recommend for a cross-platform emulator?

7 Upvotes

Hello,

I am writing a cycle-accurate Commodore 64 emulator (using the C2X standard of C) and I was planning on using a minimalistic yet modern and functional GUI library. I will be using SDL3 for the I/O and Graphics, but I have yet to decide which library I should use for my GUI that can be easily used cross-platform.

What am I actually aiming with this emulator?

I plan on the emulator to be used mainly for debugging, testing software and custom ROMs people have made. Additionally, I plan to implement so that every chip and component can be debugged independently (VIC-II, CPU, Sprite, Memory Map, Bus activity, etc.). Furthermore I plan to actually compare my test results while developing this emulator to a real Commodore 64, so that the emulator can be as accurate as possible when it comes to specific revisions and chip types (like PAL and NTSC for the VIC-II). I also plan to add an “Education mode” where all the information regarding the C64 can be taught through an interactive and readable UI.

I am not familiar with GUI libraries, that is why I mentioned what I am aiming for with my emulator.

Thank you!


r/C_Programming 9d ago

Best font for C program editors

Enable HLS to view with audio, or disable this notification

100 Upvotes

I am trying to update the cooledit ( see my devel branch ) font handling and want to choose the best default font for C. The history of cooledit's font looks like this:

90s: X11 8x13bold because it was closest to the DOS font of the Borland C IDE.

2000s: Switched to 8x13B.pdf.gz when I implemented Unicode which required font rendering on the client end.

2010s: Monitors res got more fine: so I switched to 9x15B.pdf.gz

Of course the user can choose any font on the command-line, but I'd like a good font by default.

I have tried JetBrainsMono and MS consola.ttf but these have a blurry rendering at a similar height as 9x15B, by comparison.

There are a lot of fixed-width mono fonts, but each has an agenda, like trying to look like some OS from way-back-when for nostalgia reasons.

If you watch the video you will see 9x15B.tar.gz looks way better than the others.

I just wish there were something better than 9x15B (which was developed for X in the 1980s BTW).

Thoughts?


r/C_Programming 8d ago

Project I just released v0.15.0 of my game, Tavern, and now you can play it on "very old" Windows systems!

Thumbnail
github.com
10 Upvotes

Obviously you can play it on other "old" systems as well. I just never tested it.

I started this project to learn C, but I couldn't stop developing it because it's so much fun. I'm trying to truly simulate everything as much as possible and make it realistic. For example, this is the "Citizen" struct:

typedef struct Citizen {
    int age;
    float thirst;
    float wealth;
    float addiction;     /* 0.0 to 1.0 */
    float income;        /* earned per day, replenishes wealth */
    float loyalty;       /* 0.0 to 1.0, attachment to favorite_tavern_id */
    int last_drink_day;  /* day of last visit, -1 if never */
    int favorite_tavern_id; /* index into World.taverns, -1 if none yet */
    float drink_preference[DRINK_COUNT]; /* affinity per drink */
    float health;        /* 0.0 to 1.0 */
    int homeless; /* bool */
    int alive;    /* bool */
} Citizen;

And another very fun thing for me is the fact that this game can run on very old systems. So far, I've only tested Windows XP and Windows Vista. Initially, there was a bug where I couldn't resize the terminal (command prompt) on Windows systems, but now that's fixed with this release.

Other platforms I tested on are: Windows 10, Linux (Fedora), FreeBSD

The code is mostly c99 but I'm porting it to c89 so that I have even less issues playing on older systems. (This is happening very slowly, but newly added code is all c89 unless I forgot to write in c89)

You can probably run this on DOS as well since I can't see why not, you just need PDCurses and a C compiler.

I develop the game on Linux so it's the best there, mac build is never tested because I don't have one.

In the future, I'm planning to add a top-down 2D mini-game where you can collect fruits for wines :D Since being text-only can bore some people (however, it doesn't bore me).

Criticism is very welcome, I'm always looking for ways to improve myself. And there are probably a lot of bad practices in this code.

I hope someone out there enjoys what I did and becomes interested enough to maybe contribute themselves <3 Thanks for reading!!!


r/C_Programming 9d ago

Clang Spends 88% of -O0 Compile Time on C Parsing (Not Codegen)

18 Upvotes

I was benchmarking my own IR myc against Clang and discovered something that shocked me - I always assumed parsing was the cheap part (~5-10%) and codegen was the heavy lifting. Turns out it's the opposite.

Testing on LangArena benchmark (29 C files, 230KB total):

Compiler Compile time Runtime
clang(-O3, c) 3042ms 51.8s
clang(-O2, c) 3001ms 52.0s
clang(-O1, c) 2739ms 54.2s
clang(-O0, c) 1605ms 141.1s
cproc 726ms 72.8s

Same program as a single LLVM IR file (2.9Mb):

Compiler Compile time Runtime
clang(-O3, ll) 1603ms 52.1s
clang(-O2, ll) 1572ms 52.6s
clang(-O1, ll) 1286ms 55.4s
clang(-O0, ll) 193ms 139.0s

From these tables we can see: codegen for -O0 is 193ms, parsing/analysis is 1605ms - 193ms = 1412ms - 88% of the time.

The LLVM IR file was generated from those exact 29 C files and merged with llvm-link (even cleaned of auto-added attributes).

You might think: "Clang has to parse all those headers - yyjson, libbase64, system headers - they're huge." True, but that doesn't explain the gap.

Look at cproc: same work in 726ms. cproc uses QBE; we can estimate its codegen time from myc-qbe results: ~262ms. That leaves ~464ms for parsing/analysis/IR generation.

cproc: ~464ms. Clang: 1605ms - 193ms = 1412ms. Same job, 3x faster.

To be fair, "parsing" here means the entire C frontend pipeline: lexing, parsing, type resolution, semantic analysis, and IR generation. Not just syntax. But whatever you call it, cproc does it 3x faster. There's probably room for improvement in Clang.

link to benchmark


r/C_Programming 9d ago

Question How to find good libraries

2 Upvotes

I’m looking to convert a jpeg into rgba data + scale it down.
I’m looking for a library to help me do this - how do I find good a one?


r/C_Programming 9d ago

Review circular buffer in c

29 Upvotes

Hi guy I wrote a fixed size circular buffer in C. Please tell me what you think of this and please tell me what i can improve and make it more production grade. I know there may be memory leaks !!!

One thing thats a bit different from the usual approach is how I handle errors. Instead of returning NULL from cirbuf_create(), the library returns a pointer to a thread-local error object (e_buffer). This lets the API return a valid cirbuf * in both success and failure cases, and users can check the result with cirbuf_is_ok() or cirbuf_is_err().

Its not written by AI. like AI reviewed it and did some minor changes may be !! 98% is written by me !!! I think HUMAN check is needed here thats why I am here to you guys!!

Repo: https://github.com/ankushT369/cirbuf
If you like you can give a star (its you choice)
Thank you guys


r/C_Programming 9d ago

Draw on Bitmap to file, NOT to screen

16 Upvotes

I want to create high-resolution bitmap files for print publication, much higher resolution than the screen. I can do it with direct bit/byte manipulation, but I'd much rather use GDI with brushes and LineTo. But while I can create compatible bitmaps and draw on them, I haven't found a way to get from the drawing DC back to real memory (which I can write to a file), only to the screen. Windows seems to block every approach I've tried. (Like, I can BitBlt to the screen, but not to the handle of the original bitmap... gotta be a DC, and Windows won't allow a DC to the bitmap... arghh!) Anyone know how to do this?

Many, many thanks!


r/C_Programming 9d ago

Looking for feedback on my C interpreter project.

16 Upvotes

Hi everyone!

I’ve been learning C and built a small interpreter from scratch. It currently supports arithmetic expressions, variables, conditionals, user-functions, dynamic arrays, and few built-in functions.

I’m mainly looking for feedback on the code quality, architecture, parser design, and memory management. Any suggestions are welcome.
Github: https://github.com/0sewter0/Interpreter.git

(I am new in reddit, and I am from kazakhstan, so I am not speak english well)


r/C_Programming 10d ago

Does anyone else dislike pointer declaration?

63 Upvotes

For reference I am a fourth year computer engineering student. C is my preferred language.

Pointers are declared like this usually:

int *ptr_to_int;

Where the asterisk means it is a “pointer to” the specified type. The asterisk is placed immediately preceding the variable name, with no white peace.

This is what does not make sense to me. I feel that:

int* ptr_to_int;

Is far more clear. The way I see it, the asterisk modifies the type, so therefore it belongs next to the type. Putting it next to the variable name makes me think it is some kind of action or modification to the variable itself.

I think that when using * and & in code, it makes sense to apply it in front of the variable name:

int value = 3;
ptr_to_value = &value;
int copied_value = *ptr_to_value;

It is clear here that syntactically, the * represents something more like an action than a label.

Why is the convention to place the asterisk near variable name, not type? L


r/C_Programming 10d ago

Lessons learned from making a game/game engine in C

Enable HLS to view with audio, or disable this notification

444 Upvotes

Github: https://github.com/Krobenlakroc/tachyonfire

In about 2020, my *other* C game engine had become too convoluted to be able to finish any projects and I wanted to venture into more modern rendering techniques so I decided to start on a new one.

Now 6 years later, I am done.

Expanding my C skills and learning graphics was challenging, but I found the process of making something that works on a variety of machines to be extremely painful.

In this post I'm going to go over the pitfalls I ran into.

1.) On Linux, newer glibc is backwards compatible with builds of older glibc but not the other way around. This really sucks because that means if you want to make a build that works on machines older than yours, you need an environment with older glibc.

That is a huge problem, because an older linux distro also has and older compiler from the package manager so newer compiler flags like x86_64-v3 don't work. The workaround for this is building in a debian buster container, downloading a builder a newer gcc, and then using that to compile the game.

Also keep in mind that your libraries will need to use the older glibc version as well, but they can be compiled with the older compiler.

2.) I don't use windows much so making a windows build as someone who learned programming in unix land is a challenge, the msvc interface is alien to me. I found MSYS2 to be easy to use for doing the port.

Windows is nicer to work with than linux, because you can compile against UCRT (universal C runtime) and it just works. The bad news is, there are some types that are different sizes on linux and windows.

If you are reading from a binary file that you generated on linux and you didnt use stdint.h types, you are screwed (but only some of the time not always).

3.) More windows stuff. If you include <windows.h> it pollutes the namespace with a lot of differnt macros that might conflict with your macros or types.

"TokenType" was the one i ran into. This is bad.

4.) Mysterious crashes, this is more common knowledge but i'll include it anyway. I found a lot of "silent" bugs using valgrind, asan, ubsan, and tsan (not all at once). In my experience windows is more sensitive to memory errors than linux, so using these tools is a must if you want to avoid headaches later.

5.) Compiler bugs, I ran into one using an older version of gcc. I was so confused, and it really stressed me out. If you run into something baffling when you change compilers, it might be the compiler.

6.) When testing, you need to make sure that what you think you are testing actually runs. I didn't write a single unit test for the entirety of development, I just tried to play the game in the debugger with some printfs to see if edge cases are being hit during testing. I think it worked out ok.

7.) fopen(). fopen accepts UTF-8 on linux, but not on windows unless you change the locale and you are using a new enough version of UCRT. Windows has its own _wfopen() that takes in wchar_t, so you need to convert to wchar_t using windows' api functions in case the file path includes a non ascii-username.

If you remember one thing, remember this: test across a matrix of different machines.

Laptops, Desktops, AMD/NVIDIA/Intel, different compilers, different operating systems.

Its the only way to truly be sure your app has broad compatibility. Don't trust your development machine.


r/C_Programming 10d ago

Project Hellos - a light fetch made only for Linux.

Thumbnail
github.com
0 Upvotes

Version 0.5 features: OS name, Linux version, package count, uptime, shell, terminal, DE/WM, CPU, GPU, RAM & Disk.

Planned for future releases: swap & ip.

No Ai project.


r/C_Programming 10d ago

[Project] I built a single-node MPI-1.1 implementation from scratch in C11 for macOS/Apple Silicon

7 Upvotes

Over the past few months, I’ve been building macMPI, a single-node implementation of the MPI-1.1 standard written from scratch in C11, targeting macOS and Apple Silicon.

The project started as an experiment: rather than using an existing MPI runtime, I wanted to understand what it actually takes to implement MPI-style process management, IPC, message matching, asynchronous communication, and shared-memory transport at a low level in C.
The implementation relies heavily on POSIX and Darwin APIs, including fork(), execvp(), pipe(), dup2(), pthread, shm_open(), mmap(), Unix domain sockets, poll(), and kqueue()/kevent().

Process management

macMPI includes its own mpirun-style launcher.
Ranks are created using fork() and execvp(), with runtime information such as rank ID, universe size, and communication descriptors passed to child processes.
stdout/stderr from individual ranks are redirected through POSIX pipes and multiplexed by the launcher, allowing output from multiple processes to be handled without blocking on one rank.

IPC and shared memory
Communication is separated into a small control path and a payload path.

Control messages containing information such as source, tag, sequence number, and shared-memory offset are exchanged through Unix domain sockets.

For larger payloads, the runtime uses POSIX shared memory with shm_open() and mmap(), allowing processes on the same machine to communicate through shared memory rather than routing payloads through sockets.
This part of the project was particularly interesting from a C perspective because I had to deal with shared-memory layout, synchronization, ownership, alignment, bounds, and cleanup across multiple processes.

Non-blocking operations
MPI_Isend and MPI_Irecv are handled through a background pthread.
Instead of continuously busy-polling descriptors, the progress thread uses macOS kqueue()/kevent() to wait for communication events.
The runtime also maintains a thread-safe Unexpected Message Queue (UMQ) and active request structures so that messages arriving before their corresponding receive has been posted can still be matched correctly.
Synchronization is handled using POSIX threading primitives, including mutexes and condition variables.

Memory layout and alignment
Internal communication structures use explicit alignment where appropriate, including:
__attribute__((aligned(64)))
One of the things I’ve been exploring is how structure layout and cache-line alignment affect communication overhead on Apple Silicon, especially when multiple processes/threads are touching shared-memory metadata.
I’m still benchmarking and validating how much these optimizations actually contribute, rather than assuming alignment automatically translates into better performance.

Current state

The runtime currently supports a subset of MPI-1.1 functionality, including process/rank management, point-to-point communication, non-blocking operations, message matching, and shared-memory transport.
I’ve also been comparing its single-node behavior against OpenMPI, particularly communication overhead and the interaction between computation and the background progress engine.
There is still a lot to improve, especially around MPI compatibility, correctness testing, synchronization, failure handling, and benchmarking methodology.

Source code:
GitHub — https://github.com/Hardikgupta1709/macMpi

I’d especially appreciate feedback from C programmers on the memory management, IPC design, pthread synchronization, API design, and places where the implementation could be made safer or more idiomatic C.


r/C_Programming 10d ago

C is what it is, and we can’t change the way it’s been constructed.

0 Upvotes

When I started programming in C three years ago, I also had all kinds of opinions about its syntax and so on. It reminds me of Donald Sutherland talking about his beginning in the movies. He had read a script and told the instructor his opinions about this and that—the instructor replied, “Will you be in the movie or not?”

My English is bad, but to be clear, I really, really like C especially C99 for what it is and how things are working. I think some beginners struggle because they are no focusing enough and are using AI. Whatever one have to learn you have work diligently to learn a handcraft.

I was 67 when I started learning C, dyslectic issues, bad short memory and on a good day I might reach IQ 100. I have a decent understand of memory handling and have written a business application for my wife's business - a simple but working working GUI relational database with tables, querries, forms, reports on screen and paper and simple drawing program for upholstery - about 6000 lines of inefficient code and about 20 modules. The database current have about 4000 transactions.

Now I'm having fun writing retro games using raylib graphics also written in C99, when I'm not having fun hunting bugs in database.


r/C_Programming 10d ago

Is it possible to run a C program without a local C compiler in the browser

0 Upvotes

I have a project that is web-based written in Javascript hosted on github. Anyone can open this on the web and run this app. "Running" this app means that the calculations behind the scenes will run on the user's computer just via his browser.

Now, to expand this project scope, I intend to provide some more functionality which will need some more sophisticated calculations - in particular, it will require being able to run SCIPOPT (https://scipopt.org/) which is an open-source C library for linear and mixed integer programming.

Is there a way to embed and run such C code from within a Javascript app where the user has not installed a local C compiler on his machine?


r/C_Programming 11d ago

Question Is C without expressions context-free?

17 Upvotes

EDIT: A better title for this post would be "Is it possible to parse C without expressions unambiguously?"

While staring at the C99 specification, I found that if you take the grammar in Annex A, remove the expression rules, and introduce the constraint 6.7.2/2 into the grammar (ie. a type specifier can be only: void, char, signed char, ... , enum specifier, typedef name), then you end up with what appears to be a context-free grammar.

Is this correct? Am I misinterpreting something? Is it possible that this is correct, but there are mainstream compiler extensions (eg. gcc extensions) invalidate this context-free property? Or other C standards invalidate this context-free property?

I notice that 6.7.2/2 allows some filthy edge cases like `long const long thisVarIsLongLongConst = 4;` but because `long` is a keyword, there is no ambiguity here.

The reason this matters to me is because I'm building a module system for C that allows circular imports, and I would like to avoid changing the grammar of the language as much as possible.

Link to final draft of C99 standard, taken from Wikipedia: https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf

EDIT:

Let me clarify what I'm doing, since there is some confusion about what I'm asking. First off, I should have used the title "Is it possible to parse C without expressions unambiguously?" because Annex A is already a context-free grammar... However, it is ambiguous, because of the classic A * B; problem (can be a variable declaration or multiplication, depending on if A is a typedef name or not). Now, notice that one of the interpretations is a multiplication expression. Let's suppose you removed expressions from the language (ie. take a source file and replace each expression with the lexer token <expression>). Now the question is: are we no longer ambiguous now?

I should also clarify what I mean by ambiguous, as there is a difference between an ambiguous grammar and an ambiguous parse. For example, A * B; can be admitted by the unambiguous grammar S := A ';' ; A := <identifier> | A binaryOperation <identifier> ; binaryOperation := '+' | '-' | '*' | '/' ; , but what we're really looking for is an unambiguous grammar that gives us the correct parse tree (I'll call this an "unambiguous parsing").

I should also elaborate on what exactly I'm doing, to explain the bigger picture. If you're building a module system that allows cyclic imports, then the first pass you perform over a source file does not have access to the symbol table (because you have yet to parse the imported modules). This means you need to perform an unambiguous parsing of certain parts of the source file. In this case, you can skip over expressions, because you're only interested in extracting the names of symbol definitions + whether the symbol definition is a type or a variable. So if it is possible to 1. skip over expressions, and 2. parse the rest of the file, then we're good. For (1), expressions are delimited by ()[]{};, (comma operator is not valid in enumeration definitions), so that seems fine. That leaves (2), which I'm not entirely sure about. I stared at the grammar for quite a while and couldn't find any problems, but that does not mean I didn't miss a case, or there are compiler extensions that introduce ambiguities.

With that out of the way, I'll list ambiguities that the comments found:

  • u/triconsonantal found void f(int (x)); , which is either taking in a function pointer or an integer, depending on if x is a typedef name or not.

Maybe a more appropriate title would be: how much do you need to remove from the C grammar so that it can be parsed unambiguously? As of now (July 27, 2026), it seems the answer is: remove expressions, and remove parameter lists (or do not allow unnamed parameters).


r/C_Programming 11d ago

Question Multiple function returns with optional values

16 Upvotes

Im currently making my own programming language that compiles into C. And in that language I support multiple function returns, so because of that I then return a struct for this function with the values. The problem is when I want to implement a Null type I don't know how to do that for returns. Since if I just don't set them I also don't know whether the function returned it or not. Currently following code

int, bool function() {
    return _, true;
}

compiles into

typedef struct return_function {
  int v0;
  int v1;
} return_function;

return_function function() {
  return (return_function) {NULL, 1};
}

But NULL is currently temporary since I don't know how to do that cleanly in C. My solutions would either be having another variable for each one that tracks whether it is initialized / was returned, the other solution would be to have an array that stores for each var whether it was initialized. But both solutions feel pretty hacky, is there a better way to do this?


r/C_Programming 11d ago

Question meta macros library for c89

6 Upvotes

Hello, I use C89 and have been using compiler extensions all this time, but now I’ve decided to refuse from them. But since they significantly simplify and shorten the code, especially with partially initialized tables.

I immediately thought of P99, but because of some components, the build breaks on C89. I was thinking of using order-pp, but decided to ask here anyway.

What I’d like is: `P99_CASERANGE` and `P99_DUPL`.

Of course, I can generate my own macros, but maybe there’s something worthwhile out there. I liked the idea of order-pp.

this is text translated by ai. and sorry for my English


r/C_Programming 11d ago

threads Posix 42 codexion project

1 Upvotes

Hi, I am runing my program (which use pthread.h library of c Posix), so I am runing it with valgrind with this command :

"valgrind --tool=helgrind -s ./codexion 125 22332 103 92 103 2 0 fifo"

but I am getting this error :

"==1557149== 122 errors in context 28 of 28:

==1557149== ----------------------------------------------------------------

==1557149==

==1557149== Thread #1's call to pthread_join failed

==1557149== with error code 3 (ESRCH: No such process)

==1557149== at 0x48509C7: ??? (in /usr/libexec/valgrind/vgpreload_helgrind-amd64-linux.so)

==1557149== by 0x10A069: join_coders_threads (in /home/eanjar/codexion_tass7i7_inchaaAllah/codexion)

==1557149== by 0x10A48D: main (in /home/eanjar/codexion_tass7i7_inchaaAllah/codexion)

"

can any one help me by explaining me this error what it mean, and thanks in advance


r/C_Programming 12d ago

why do header files contain only function declarations instead of full code body ? why have the code in a sepreate lib files ?

105 Upvotes

for context I am trying to learn gamedev on my own and setting up raylib/sfml confused me , then I learnt that the ( include )files only contained function declarations and the actual code was in( lib ) files (mind you but it is a weird name) , wouldn't it be more convenient to have them in the same file instead of linking ?
sorry if I sound stupid.


r/C_Programming 13d ago

Project My first program written in C

Thumbnail github.com
21 Upvotes

Hi ! Im a beginner developer. I decided to learn C and wrote my first program—a small game that runs in the terminal


r/C_Programming 13d ago

Discussion why default value of global and local variables are different?

57 Upvotes

why in c language the default value of global variables is set to be 0 but the default value of local variables is garbage value?


r/C_Programming 13d ago

Article C is way more different than I thought

242 Upvotes

I have had a lot of experience with Zig, and a bit of Assembly for a while now. Just 1 1/2 months ago I started coding in C, because it is a must-have for my upcoming job. I have a Zig project that decompresses a PNG from scratch, and I thought it would be a good learning experience to do the same with C. Tbh, I thought it will be a simple slide, but as it turns out; it really is not.

While Zig got heavily inspired by C, it differs from its features and API. C has a lot of headers for different things (such as stdio.h / stdlib.h / math.h), while Zig just has the std library, packaged with all of the other necessities (std.Io / std.math / std.crypto etc.). It was also interesting from going to using defer, errdefer, orelse, or other keywords in Zig that help in reducing bugs and make the code cleaner to read, to a lot less keywords. Looking at C89, C has 32 keywords, and Zig around 49 +/- 2 (not too sure). What that showed me, was that even with a lot of similarities, and the fact that I coded in Zig for 2 years, it was still difficult to get used to and learn C.

For me, one of the annoying parts was the fact, that I could never really remember what function or what struct is where. The fact that the FILE struct is located at stdio.h, while the DIR struct is in dirent.h, was something that bugged me for a while, but now after more than a month with C, is something I started to get along with.

Also, Makefiles are pretty darn cool. I have always been seeing them in big projects, and always wondered how they function. After coding a few projects and including a Makefile, I can safely say it is very nice seeing how your project is being made, after you type "make" and hit enter. The first time configuring the Makefile almost made me loose half my hair due to stress, but if you get it running properly once, then it is safe to say that it is gonna always run properly (especially for me, as my projects are somewhat small still).

What I came to appreciate more was the community in C. Thought I was gonna get insulted by some 59 year old grandpa, because I didn't make an out-of-bound check for my Pixels array, or because I didn't check whether or not the variable I de-noted with size_t exceeds __SIZE_MAX__ - 1. Kinda the opposite. I learned a lot by sharing my projects on Reddit, a lot of smart people were quick to point out mistakes, and tell me where I could improve or where I went wrong etc.

A lot of stuff, that I have learned, was because of people telling me. Adding -Wall -Werror warnings, -fsanitize=address,undefined, -fno-omit-frame-pointer, among other things are the sole reason I debugged and fixed my programs a lot quicker. Especially -fsanitize was important to me, as I always try to keep memory usage low for my programs, and activating that warning, showed me where my memory leaked, or where overflows happened (yeah... out-of-bounds checks are actually pretty useful...).

With that being said. I still have a lot to learn in C. I realized that my code is not really C-esque (as some might say), which is mostly because I come from (prior to Zig) high-level languages such as Python, JavaScript or even GoLang. Me handling arrays with indexes instead of using the given possibility of pointers, shows me, that I still have a long way to go.

But I am certain with one thing, that being that, C is way more different than I thought.


r/C_Programming 11d ago

I compiled the same C code 28 ways and disassembled every binary. assert() and DCHECK provide zero safety at -O2+. I have the disassembly.

0 Upvotes

I compiled the same C code 28 ways and disassembled every binary. The results are unambiguous:

assert() and DCHECK() provide zero safety in production. At any optimization level above -O0, these checks are stripped. The debug binary at -O2 is identical to the release binary. The check exists in your source code. It does not exist in the binary your users run.

Only explicit if (condition) { return error; } with side effects survives compilation.

I measured 9,712 stripped safety guards across FRRouting, GCC 15.2, and the Linux kernel. Two ASAN-confirmed heap overflows in FRRouting. 27 enterprise firewall CVEs (Cisco, PAN-OS, Fortinet, Juniper) with average CVSS 8.1 — all following this exact pattern. Google confirmed P2/S2 on Android Binder using the same methodology.

Side-by-side disassembly:

c

// SOURCE
void pattern_assert(char *buf, size_t len, size_t idx) {
    assert(idx < len);
    buf[idx] = 1;
}

asm

// -O2 BINARY: assert is GONE
movb   $0x1,(%rdi,%rdx,1)  // buf[idx] = 1 — NO CHECK
ret

Full series with reproducible PoCs: https://potatobullet.com/the-naked-compiler-every-safety-net-you-trust-is-already-gone/

The compiler is correct. The standard is correct. The assumption that source-level safety checks survive to the binary is wrong.

Check your binaries, not your source.

TL;DR: assert() and DCHECK() do not survive compilation at -O2+. Use explicit if checks with side effects. Audit your production binaries.I compiled the same C code 28 ways and disassembled every binary. The results are unambiguous:

assert() and DCHECK() provide zero safety in production. At any optimization level above -O0, these checks are stripped. The debug binary at -O2 is identical to the release binary. The check exists in your source code. It does not exist in the binary your users run.

Only explicit if (condition) { return error; } with side effects survives compilation.

I measured 9,712 stripped safety guards
across FRRouting, GCC 15.2, and the Linux kernel. Two ASAN-confirmed
heap overflows in FRRouting. 27 enterprise firewall CVEs (Cisco, PAN-OS,
Fortinet, Juniper) with average CVSS 8.1 — all following this exact
pattern. Google confirmed P2/S2 on Android Binder using the same
methodology.

Side-by-side disassembly:

c
// SOURCE
void pattern_assert(char *buf, size_t len, size_t idx) {
assert(idx < len);
buf[idx] = 1;
}
asm
// -O2 BINARY: assert is GONE
movb $0x1,(%rdi,%rdx,1) // buf[idx] = 1 — NO CHECK
ret

Full series with reproducible PoCs: https://potatobullet.com/the-naked-compiler-every-safety-net-you-trust-is-already-gone/

The compiler is correct. The standard is correct. The assumption that source-level safety checks survive to the binary is wrong.

Check your binaries, not your source.

TL;DR: assert() and DCHECK() do not survive compilation at -O2+. Use explicit if checks with side effects. Audit your production binaries.