In release fast mode, unsigned overflow/underflow is undefined in Zig whereas in C it wraps.
:-)
Of course C has many UBs that Zig doesn't have, so C is far less safe than Zig, especially since you can use ReleaseSafe in Zig..
D does not decay arrays, so D has array bounds checking.
Note that array overflow bugs are consistently the #1 problem with shipped C code, by a wide margin.
This isn’t strictly true, a C implementation is allowed to associate memory-range (or more generally, pointer provenance) metadata with a pointer.
The DeathStation 9000 features a conforming C implementation which is known to catch all array bounds violations. ;)
3.1.2.5 Types
[...] A computation involving unsigned operands can never overflow, because a result that cannot be represented by the resulting unsigned integer type is reduced modulo the number that is one greater than the largest value that can be represented by the resulting unsigned integer type.
Source: C89 (draft) at https://port70.net/~nsz/c/c89/c89-draft.txt int main() {
int a[3];
return foo(a);
}
> gcc test.c
> ./a.out
Oops.D: int foo(int[] a) { return a[5]; }
int main() {
int[3] a;
return foo(a);
}
> ./cc array.d
> ./array
core.exception.ArrayIndexError@array.d(1): index [5] is out of bounds for array of length 3
Ah, Nirvana!How to fix it for C:
>Possible undefined behavior ranges from ignoring the situation completely with unpredictable results ... or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message)
So a compiler is absolutely welcome to make undefined behavior safe. In fact every compiler I know of, such as GCC, clang, MSVC has flags to make various undefined behavior safe, such as signed integer overflow, type punning, casting function pointers to void pointers.
The Linux kernel is notorious for leveraging undefined behavior in C for which GCC guarantees specific and well defined behavior.
It looks like there is also the notion of unspecified behavior, which gives compilers a choice about the behavior and does not require compilers to document that choice or even choose consistently.
And finally there is what you bring up, which is implementation defined behavior which is defined as a subset of unspecified behavior in which compilers must document the choice.
That actually really does exist already with CHERI CPUs, whose pointers are tagged with "capabilities," which catch buffer overruns at runtime.
https://tratt.net/laurie/blog/2023/two_stories_for_what_is_c...
https://msrc.microsoft.com/blog/2022/01/an_armful_of_cheris/
I personally find that googling stuff provides not much connection to the subject of study, very impersonal and try to avoid it.
For example I did google the concept, and found this https://github.com/cousteaulecommandant/ds9k.
Which is not trivial to parse, bing posited the answer as authoritative, and if you look at the code it is really nothing, it seems to be a folklore concept, and as such, it is much more aptly transmitted by speaking to a human and getting a live version than by googling an authoratitative static answer.
int foo(int (*a)[6]) { return a[5]; }
int main() {
int a[3];
return foo(&a);
}
Or for run-time length: int foo(int n, int (*a)[n]) { return (\*a)[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), &a);
}
/app/example.c:4:38: runtime error: index 5 out of bounds for
type 'int[n]'
https://godbolt.org/z/dxx7TsKbK\*Writing safe code is better than depending on safety features. Writing safe code is possible in any programming language, the only things required are good design principles and discipline (i.e. solid engineering).
int foo(int n, int (*a)[n]) { return (\*a)[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), &a);
}
That syntax is why array overflows remain the #1 problem with C bugs in shipped code. It isn't any better than: int foo(size_t n, int* a) { assert(5 < n); return a[5]; }
int main() {
int a[3];
return foo(ARRAY_SIZE(a), a);
}
as the array dimension has to be handled separately from the pointer.Contrast with how simple it is in D:
int foo(int[] a) { return a[5]; }
int main() {
int[3] a;
return foo(a);
}
and the proof is shown by array overflow bugs in the wild are stopped cold. It can be that simple and effective in C.