Difference between `zig cc` and `zig build-exe` for c source code

I have a source code which builds successfully with zig cc, but not zig build-exe.

What would be the right way to debug this problem ? I guess these two commands use slightly different flags, but cannot figure it out. Here are the compilation results:

raspberrypi:~/makeheaders $ zig cc -lc makeheaders.c
raspberrypi:~/makeheaders $ ./a.out
Illegal instruction
raspberrypi:~/makeheaders $ zig cc -lc -g -O2 makeheaders.c
raspberrypi:~/makeheaders $ ./a.out
raspberrypi:~/makeheaders $ zig build-exe -lc -cflags -g -O2 -- makeheaders.c
raspberrypi:~/makeheaders $ ./makeheaders
Illegal instruction

Please note that zig cc needs -g -O2 flag to work.

The source code is makeheaders.c



There are many warnings when compiling with gcc. I suggest that it is possible that your C code exercises the compiler too much.


makeheaders.c contains some code that causes undefined behavior (UB), specifically in the Hash function. It tries to shift h five bits to the left without checking if the result would fit in an int. This would be fine if h were unsigned, but because int is a signed type, we get UB! (At the bit level, you don’t want to shift 1 bits into or past the sign bit of a signed integer.)

Changing the int to an unsigned int should work:

--- makeheaders.c.bak	2023-11-09 15:30:19.732884800 -0500
+++ makeheaders.c	2023-11-09 15:32:45.913824400 -0500
@@ -487,7 +487,7 @@
 ** value between 0 and 2**31 - 1
 static int Hash(const char *z, int n){
-  int h = 0;
+  unsigned int h = 0;
   if( n<=0 ){
     n = strlen(z);

I’m not sure if there’s more problems lurking somewhere.

As for the differences in those three Zig commands:

  • zig cc -lc makeheaders.c: Debug builds are the default in Zig, and Zig enables Undefined Behavior Sanitizer (UBSan) for debug builds. I guess Zig doesn’t support the fancy runtime library that prints pretty error messages, so instead an illegal instruction is executed (like ud2 on x86).

  • zig cc -lc -g -O2 makeheaders.c: In this case, Zig parses -O2 and “understands” that you want a release build. It disables UBSan, and your program will hopefully work just fine.

  • zig build-exe -lc -cflags -g -O2 -- makeheaders.c: I think Zig blindly passes the arguments between -cflags and -- to Clang, so it doesn’t “understand” that you want a release build and leaves UBSan enabled. If you do zig build-exe -lc -O ReleaseFast -cflags -g -- makeheaders.c, UBSan should be disabled.

By the way, I think you can explicitly disable UBSan using -fno-sanitize=undefined for zig cc or -fno-sanitize-c for zig build-exe. -fno-sanitize=undefined should also work in -cflags.

P.S.: Funnily enough, Hash masks out the sign bit before returning to ensure the return value is non-negative. I don’t understand why Hash does that instead of just using unsigned int.

1 Like

Oh yeah, to answer:

Try using a debugger. That’s how I determined that Hash was problematic. At the very least, using a debugger can help you narrow down the bug to a single function or statement or instruction, which is a good starting point for more investigation :^)

1 Like

Thanks for the explanation and suggestions. I thought it is more of a flag issue. If it is caused by the code, I will try to debug.