As Richard Harter has said, to some extent it's just a consequence of
the over-use of one keyword. But there is some logic to the
distinction: Evaluating an expression and throwing way the result is a
commonly done thing (although most often done implicitly).
Dereferencing a pointer to get nothing is a useless activity.
What I have attempted to suggest over and over again is that it should
be simply another form of void expression. We have two already, why
not three? Please consider:
static int i = 15;
void *func(void) {
i++;
return &i;
}
int main(void) {
*func();
return 0;
}
Compare 'main()' to this one:
int main(void) {
(void)func();
return 0;
}
My personal aesthetic would be for the former, rather than the
latter. I have suggested that the unary '*' operator defines the
result of '*func()' above to have type 'void'. Since this is known to
be a void expression (at translation time), and void expressions are
evaluated for their side effects, what is the problem here? In this
context, the unary '*' would simply mean "throw away the returned
'void *'". To me, that would be useful, not useless.
Putting aside the words of the standard for a moment, the purpose of
evaluating an expression of the form *E is to recover a function
designator or an object from a function pointer or an object pointer.
Sure. I even agree that that's the only way anyone I've ever known to
use it has used it. It just so happens that another purpose might
yield a pleasantry. Somewhat akin to the multiple purposes of 'void'
in the first place (the "hack").
Since void is not an object type, applying * to an expression of type
void * is counter-intuitive and goes against the main point of the
operator. C chooses to make it undefined.
Ok, but what about the referenced draft? It appears to be well-
defined there as a void expression.
They both talk about type, but the way the type is used is very
different. A cast converts and a pointer dereference interprets.
This is an interesting perspective to me and I will try to follow
along your train of thought, here.
Given
E of type T *, *E does not do any conversion (see the common trick of
"type-punning").
Agreed. No conversion of a value. However, if you consider taking an
operand of one type and returning a result with another type, that
might be considered to be a conversion of type. Would you agree?
I'd say is true at some level, but since there is no defined result,
it's type is not really relevant.
My reasoning goes:
1. A type for the result is defined.
2. That type is defined to be 'void'.
3. Such a result is a void expression.
4. By 1, 2, 3, it is defined to be a void expression.
Do you perceive a flaw in this reasoning for any of those points?
Compare to casting to 'void':
1. A type for the result is defined.
2. That type is defined to be 'void'.
3. Such a result is a void expression.
4. By 1, 2, 3, it is defined to be a void expression.
Essentially, does any other treatment not need to pull in that 'void
*' is not a valid type for an operand of unary '*' from somewhere?
Does any other treatment not need to suggest that the operand
_shall_only_ have type pointer-to-function-type or type pointer-to-
object-type?
It's like saying the result of 1/0 is
of type int. Yes it in is some sense, but since it is undefined the
type could be double[7]. When it is not evaluated (and the UB is
avoided) it does then have a type, but so does *(void *)E.
We are in agreement, here. Also note that in:
int i = 15;
void *p = &i;
void *q;
q = p;
*p;
Evaluation of 'p' is defined, because we can assign it to 'q'. So at
what point exactly during evaluation of the last line does the
undefined behaviour creep in? If 'p' had an invalid value
(uninitialized, unassigned), surely that would be UB.
Only if you accept that a result can be nothing but a type and if you
reject the "undefined by omission" clause.
Does the result of casting to 'void' not have nothing but a type? Is
not the result of calling a "function returning void" one with nothing
but a type? I readily accept the "undefined by omission" clause. But
is it not _included_? "If the operand has type ‘‘pointer to type’’,
the result has type ‘‘type’’." 6.5.3.2,p4. Are 'void' and 'void *'
not both types?
Consider this:
The best slogan will win a car! The make of car will be the one
your slogan refers to.
What is the result of submitting the losing slogan "Fords are OK"?
The losing slogan is evaluated for its side effects (judges'
consideration) and the prize to the loser has no value and cannot be
used in any way. Is that not so?
Some people could take offence at this and, at the same time, it is
unlikely to prevent ill-considered replies.
What do _you_ perceive as potentially offensive about this request? I
cannot fathom how this could be offensive at all. I explain directly
thereafter that I believe that if major overhauls or negative
implications are perceived, that we are less likely to reach an
agreement. So if someone takes their time to consider the
implications, not only can they offer a nice set of implications, but
I can have some degree of certainty that their agreement was not based
on a quick read.
Some bugs (such as the above) would go undetected.
I think what you are suggesting here is that if we agree on well-
defined behaviour and treat it as a void expression, that a programmer
might not have intended it and will have a tough time finding it.
Suppose, for a moment, the a programmer had forgotten the type of
'p' (thought it was 'int *') as well as the prefix increment
operator. That might be a reasonably common error (it's two
keystrokes away from '*p;'). Constraint 6.5.3.1,p1 catches it.
Another "two-keystroker" would be a function call with no arguments.
If the programmer tried this and the function did not have type
"function returning pointer-to-void", we should catch either a "result
unused" or an attempt to dereference something not a pointer, perhaps.
No problems, with those.
What is the gain
that makes this loss acceptable? In effect, this is my suggestion in
another form: what useful program needs to apply * to a void *?
static int i = 15;
void *func(void) {
i++;
return &i;
}
int main(void) {
int *j;
int k = 5;
j = func();
/* ... */
j = &k;
/*
* We don't have a pointer to 'i' any more, but wish to increment
it.
* We know that 'func()' does this, but we are using 'j' for 'k'
indirection.
* Call 'func' and discard its result.
*/
*func();
return 0;
}
Thanks, Ben.