7 ms·
> 6. EXAMPLE Code that checks to see if a pointer value is null cannot be omitted during translation solely on the basis that the pointer is dereferenced before
by geofft 8y ago
> 6. EXAMPLE Code that checks to see if a pointer value is null cannot be omitted during translation solely on the basis that the pointer is dereferenced before the check and that earlier dereference would produce undefined behavior if the pointer value was null.
Here's the thing I don't quite understand: I gather that this is advocating for something like GCC's -fno-delete-null-pointer-checks to be part of the spec, but, how is -fno-delete-null-pointer-checks actually helpful?
I think it's because if you write code like this:
void foo(struct foo *param) {
int n = param->length, i;
if (param == NULL) {
return;
}
for (i = 0; i < n; i++) {
do_something(param->data[i]);
}
}
with -fdelete-null-pointer-checks the compiler might optimize it in two successive steps to delete the check (because param->length was already dereferenced) and then move the dereference down to the beginning of the for loop, and with -fno-delete-null-pointer-checks the compiler might skip the first optimization and keep the second.
But for -fno-delete-null-pointer-checks, you're relying on the second optimization, moving the dereference later. Otherwise you'd just have segfaulted already (or, in a kernel context, the read might have succeeded from a userspace page mapped at the null page). Right?
It seems reasonable for the compiler (or some other tool) to produce a hard error here and prevent this code from compiling, saying, hey, this is clearly doing something unwanted. It doesn't seem reasonable to expect the compiler to do certain optimizations to make your code safe, without which your code would have a security vulnerability, and also get mad at the compiler for doing more optimizations that return it to the original state of things.
> The limitation of lvalue access by types has never made much sense and required a undefensible exception for character pointers.
My understanding of type-based alias analysis is that it's required for code like this to optimize the way you'd want:
struct string {
size_t len;
char *data;
}
void copy_string(struct string *dest, struct string *src) {
size_t i;
if (dest->len < src->len) return;
for (i = 0; i < src->len; i++) {
dest->data[i] = src->data[i];
}
}
Otherwise the compiler would have to worry that dest->data would overlap with src->len and re-read it every time!
You can do this without type-based alias analysis in a language that gave you different tools for managing variables, but doing so backwards-compatibly in C seems difficult, and this proposed rule does not seem to help.
- deleted 8y ago[deleted]
- iainmerrick 8y agoWell, what benefit does -fdelete-null-pointer-checks bring? Why would I ever want to remove that check? The more conservative version either segfaults or (quite likely) accidentally does the right thing. That situation is not great but it’s better than nothing. The fancy optimization just makes a not-great situation much worse. I agree, it would be best if the compiler rejected this as clearly incorrect code. I think that needs a change in the spec relating to undefined behavior, though (and more importantly a change in direction from compiler vendors). Edit to add: your second example, type aliasing, is a good one too. I think that one could also be usefully addressed by a compiler that flags it as a likely error. Although you’d need some way to annotate the code in the event that the weird aliasing behavior is actually correct and expected (I dunno, make “len” volatile or something?)
- geofft 8y ago> Well, what benefit does -fdelete-null-pointer-checks bring? Why would I ever want to remove that check? The more conservative version either segfaults or (quite likely) accidentally does the right thing. That situation is not great but it’s better than nothing. My concern is largely about how to phrase "The compiler may not delete this dead code" in a way that's meaningful. For instance, I assume the intent of the proposal is not to forbid optimizing this code in the obvious way, right? int n = param->length, i; if (param == NULL) { return; } if (param == NULL) { return; } for (i = 0; i < n; i++) { do_something(param->data[i]); } What about this? int n = param->length, i; for (i = 0; i < n; i++) { if (param == NULL) { return; } do_something(param->data[i]); } which is more likely to show up in the form of an inline function that checks for NULL - can the compiler refactor that check into a single one? > I think that one could also be usefully addressed by a compiler that flags it as a likely error. The compiler can't know when compiling that one function whether src->len and dest->data alias. It might be compiling that file as a standalone object file, with no other context about who's calling it. Should it emit a warning when compiling that code? (What's the warning, "I'd like to optimize this but I can't?") Alternatively, should it flag the function ... somehow ... so that when someone else calls it with aliasing arguments, that's an error? That isn't even a capability available to the C language as written: it's valid to call that function knowing nothing other than the struct definition and the function's prototype. You can imagine a language where variables carried information about whether they alias. (Actually you don't really have to imagine; the `restrict` keyword does a subset of this in C itself, and other languages have much more explicit ways of tracking this, such as Rust's shared vs. mutable references.) But it would be hard to retrofit such a thing onto existing C code. The fundamental problem with the C language working group is that they're obligated to keep working on C as it already exists.