int main() {
int arr[4] = {1, 2, 3, 4};
arr[10] = 5;
return 0;
}
Running zig build-exe -lc .\main.c will still produce the executable. I would’ve expected instead, that there’d be an index out of bounds error such as shown in similar to Zig code
thread 1 panic: index out of bounds: index 11, len 10
/app/example.zig:30:25: 0x1038bb7 in push_back (output.s)
vec.m_buffer[vec.m_len + 5] = val;
^
C code does not have the same checks as Zig code - it’s a different language.
It’s also important to point out that if you’re using const data, you could be getting compile time checks that are surprising you.
Make sure to compare apples to apples - you may be comparing what’s happening at comptime to runtime.
You can certainly index out of bounds for a slice, but in debug mode it will trigger a panic. If you try it at comptime, it may catch it and report it back while it’s building.
Gotcha. I’m still a little confused though as to how it produces a binary.
Given the following C code:
#include <stdio.h>
int main() {
int arr[4] = {1, 2, 3, 4};
arr[10] = 5;
printf("Hello world");
return 0;
}
Running the same compiler command produces an executable, however running the executable does not print “Hello world”.
So, is the binary compiled just a “shell”? Or something else?
Zig compiles C code with clang (the zig compiler currently bundles clang’s main function inside of it), so the question is “why does clang not do bounds checking?” to which I don’t have an answer based on first-hand experience, but according to stack overflow you’re supposed to use ASAN with the issue that it won’t catch if your OOB access happens to land in valid memory.
There’s one which is work in progress (arocc by Vexu), but it’s not complete enough yet to replace clang and even once that happens, C just inherently sucks in terms of safety so I wouldn’t hold my breath on any groundbreaking improvement on that front. Zig is not in the business of redefining the C standard so if pointer math that lands you into another array is meant to work, chances are that this won’t change with whatever C compilier Zig will bundle.
And observe that the compiler indeed throws a descriptive, pertinent warning on your code:
Output:
<source>:5:5: warning: array index 10 is past the end of the array (that has type 'int[4]') [-Warray-bounds]
5 | arr[10] = 5;
| ^ ~~
<source>:4:5: note: array 'arr' declared here
4 | int arr[4] = {1, 2, 3, 4};
| ^
1 warning generated.
Compiler returned: 0
While it’s far from perfect, with the correct warning flags enabled you’d be surprised how much the compiler can figure out. Observe this other example:
With the addition of -Warray-bounds-pointer-arithmetic, it can also catch a pointer arithmetic flub. Note how it doesn’t catch an identical flub in the function badFunction() though. It wasn’t smart enough to figure out the pointer input to that function originated from an array of length 4.
That is interesting. So I wonder what clang is doing because it still creates an executable, but it seems like it’s just an “empty shell” executable since it doesn’t print “hello world”.
Also, I wonder if there’s a way to add compilation flags to clang if you’re compiling with the Zig compiler.
So, by default, warning flags are NOT treated as compile errors. If you add the flag -Werror this will cause any warnings to stop compilation with an error. So what you’ve done is created an executable that rapidly hits Undefined Behavior! It isn’t an “empty shell” or anything, it’s simply doing exactly what you told it to and likely hitting a segfault trying to access memory it isn’t supposed to.
And you can absolutely add compilation flags! It would go in this part of your build.zig: