What's integer divide by zero in C? Would you consider that safe?
This is true, but it could be argued that it's harder to avoid signed integer overflow in C/C++ than it is to avoid buffer overflows: you often don't know what types you're working with, due to the usual arithmetic conversions and integer promotions, and checking whether an overflow will occur without causing an overflow is difficult in itself. It's kind of uniquely awful in this respect, basically every other popular language has a more practical integer programming model.
It's explicitly left undefined.
> Would you consider that safe?
Yes, because, as all C newbies can easily explain to you, the general rule of thumb is that undefined behavior should be treated as a fault and thus should be handled as a bug.
Hence, your question reads as "Would you consider a bug to be safe?".
In case of integer division, you simply need to check that the divisor is not zero prior to executing the division. Done.
People not new to C have noticed that so many constructs are UB as to make it infeasible in practice that any given codebase will be free of UB.
What makes C a fundamentally unsafe language is that conceptually minor errors have unspecified consequences of unbounded magnitude, limited to no compile time detection of said errors, and that even builtins like + are specified to compile successfully into nonsense in some contexts.
Integer operations can be defined to be safe. Whatever integers you pass to your maths operation, you get an unsurprising integer back.
What the language does in the presence of your bug is definitely part of the safety properties of the language.
There, fixed that for you.
Everybody knows that these scary stories can happen (even though almost nobody has seen them happen in the wild). But for the most part they should be seen as a combination of (typically, obviously) buggy code and compiler optimizer defects, rather than fundamental defects of the language.
> Integer operations can be defined to be safe. Whatever integers you pass to your maths operation, you get an unsurprising integer back.
There is at least 1 arithmetics teacher disagreeing with you.
There is no point, and let me scream again NO FR**G POINT, for a zero divide to return zero. I want it to crash.
And I consider it a compiler defect if the compiler proves that a zero division is happening and proceeds to do a strange optimization instead of reporting it.
It's a fine line to walk though, since there is also the case of legitimately assuming that it doesn't happen, and not emitting the code that triggers the crash. There probably should be compiler knobs to tune the behaviour.
C intentionally leaves behavior undefined to allow flexibility in implementation, which allows implementations to take advantage of optimizations that other languages can't because they are over-constrained for the target architecture.
What's the order of operations for sequence of expressions added together? Do they evaluate left to right or right to left? The answer is, very intentionally, "any order is allowed and order may change between executions of a line." This makes it possible to make C implementations equivalently fast on various architectures where, for example, stacks and stack operation ordering make it more efficient to do one or the other. Hell, the language is even usable in an architecture where each of those expressions could be run purely parallel. Of course, the downside is that you definitely can't assume side effects caused by the left side of an expression occurred before the right side.
But the trade-off is that you do have to be extremely sensitive to making assumptions that the specification doesn't actually specify, or you are going to trip over an error in your assumptions that matters on your implementation. The actual "this is undefined behavior so the program deleted your whole hard drive" scenario is incredibly unlikely on a modern desktop or server architecture, but the languages flexibility does mean that you're perpetually one missed trick away from incredibly perplexing behavior.
This is a feature of the language not a bug. It's how the language gets you as close as it possibly can to the speed you would be able to get hand writing the assembly at every step of the process.
Whether or not this is a common issue on commodity hardware is basically irrelevant given C's remaining niche. It's mainly the language of kernels, drivers, and firmware nowadays, not primitive CLI tools that can crash whenever they feel like it. Ensuring system state with high reliability is the raison d'etre of modern C, so the lack of ways to do that without significant caveats is incredibly problematic.
> It's mainly the language of kernels, drivers, and firmware nowadays, not primitive CLI tools that can crash whenever they feel like it
On this we completely agree; I would encourage pushing implementation of command line tools in C as close to zero as possible. It isn't necesary to get maximum performance (either because those tools don't demand it or because we have other langauges that make better safety-performance tradeoffs to get us, on average, as fast as C without sacrificing things like memory-access safety).
You can say things about the expected input and output, you can reason about how it's supposed to get there, and you can do traces of a discrete path that was taken in hindsight. But being able to pause the universe and say "What is the current state of the machine" isn't possible when the 'machine' is spread across data centers in multiple geographic locations and it's barely, if we turn our head and squint and lie to ourselves, possible on a modern CPU architecture with SIMD instructions and branch prediction.