Associativity of unary C Operators

D

dspfun

I'm trying to get a good understanding of how unary operators work and
have some questions about the following test snippets.

int *p;
~!&*++p--;
It doesn't compile, why? The problem seems to be the ++, the compiler
says: "Error: invalid l-value in increment".

int i = 10;
~!*&i++;
It doesn't compile, why? The problem seems to be the ++, the compiler
says: "Error: invalid l-value in unary "&"".

int *p;
~!-&*p--;
It doesn't compile, why? The problem seems to be the -, the compiler
says: "Error: invalid type of argument to unary minus".
I guess the problem is that you can't do unary minus on pointers, and
it doesn't make sense to do so either.

The following does compile on the other hand:
int *p;
p = (int *) = 0x10101010; /*Just to set it to some value..*/
~!&*p--;
p results in having the value 0x1010100c which I understand why.

Is it possible to make an expression with all the unary operators one
after another?

In general, is it good programming practice to make use of the
precedence and associativity of operators?

Using precedence/associativity of operators when combining the
operators (probably) reduces the number of codelines (compared to
"spelling it out"), but doesn't it make the source code more difficult
to read?

Isn't it better to instead "spell it out"?

Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

Quite a few questions, hmm, hope someone will be able to respond..!

BR!
 
R

Richard Heathfield

dspfun said:
I'm trying to get a good understanding of how unary operators work and
have some questions about the following test snippets.

int *p;
~!&*++p--;
It doesn't compile, why?

Because ++ only works on objects, and the result of p-- is an expression,
but not an object. Other problems: p has no value, so p-- invokes undefined
behaviour. It points nowhere in particular, so even if ++p-- were
meaningful (which it is not), *++p-- would be dereferencing an
indeterminate pointer value - undefined behaviour again.
int i = 10;
~!*&i++;
It doesn't compile, why?

Because & only works on objects, and the result of p-- is an expression, but
not an object.
int *p;
~!-&*p--;
It doesn't compile, why?

Because unary minus only works on arithmetic types, and int * is not an
arithmetic type.
The following does compile on the other hand:
int *p;
p = (int *) = 0x10101010; /*Just to set it to some value..*/

No, it doesn't. Drop the second =.
~!&*p--;
p results in having the value 0x1010100c which I understand why.

The behaviour is undefined because p's value is changed to an invalid value.
Is it possible to make an expression with all the unary operators one
after another?

Probably not. What possible point could there be to it?
In general, is it good programming practice to make use of the
precedence and associativity of operators?

It is good programming practice to be aware of the precedence and
associativity characteristics of C, but to make a maintenance programmer's
job easier it is generally best to use parentheses rather than insist on
the maintainer knowing K&R2 p53 off by heart. It is, however, generally
considered necessary for all C programmers to be aware that multiplication
and division take precedence over addition and subtraction, however, so
there is no need to parenthesise the multiplication in v = a + b * c + d.
Using precedence/associativity of operators when combining the
operators (probably) reduces the number of codelines (compared to
"spelling it out"), but doesn't it make the source code more difficult
to read?
Yes.

Isn't it better to instead "spell it out"?
Yes.

Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

No. I don't generally think of "when would I use such-and-such an operator
with such-and-such another operator?". Each operator is a tool. I know when
I'd use a saw, and I know when I'd use a screwdriver, but the question
"when would I use a screwdriver with a saw?" doesn't really mean a lot.

Saving code lines as an end in itself is only ever a goal in IOCCC. Write
for clarity, not brevity.
 
S

Stephen Sprunk

dspfun said:
In general, is it good programming practice to make use of the
precedence and associativity of operators?

Using precedence/associativity of operators when combining the
operators (probably) reduces the number of codelines (compared to
"spelling it out"), but doesn't it make the source code more difficult
to read?

Isn't it better to instead "spell it out"?

The general rule of thumb is that if you're forced to look at a
precedence table to figure out which operator goes first, you should add
parentheses to make it clear, which helps avoid mistakes.
Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

Saving code lines should never be a goal; the goals are to make the code
(a) work correctly and (b) easy to maintain.

The primary audience for your code is other programmers; what the
compiler thinks of it is secondary. Write to the correct audience.

S
 
M

Martin Ambuhl

dspfun said:
I'm trying to get a good understanding of how unary operators work and
have some questions about the following test snippets.

int *p;
~!&*++p--;
It doesn't compile, why? The problem seems to be the ++, the compiler
says: "Error: invalid l-value in increment".

Applying ++ to p-- is applying it to a value, not an object, but the ++
operator has a side effect which makes sense only when applied to a
modifiable object.
int i = 10;
~!*&i++;
It doesn't compile, why? The problem seems to be the ++, the compiler
says: "Error: invalid l-value in unary "&"".

i++ is not an object: it has no address for the & operator to return.

[etc]


In general, is it good programming practice to make use of the
precedence and associativity of operators?

It is good programming practice to use operators only in contexts for
which they make sense. Why are writing arcane expressions like these
when you don't have a clue what the operators do?
 
D

dspfun

Thanks for your repsonse! However, I'm not sure I follow what you mean,
please see below.

Richard Heathfield skrev:
dspfun said:


Because ++ only works on objects, and the result of p-- is an expression,
but not an object. Other problems: p has no value, so p-- invokes undefined
behaviour. It points nowhere in particular, so even if ++p-- were
meaningful (which it is not), *++p-- would be dereferencing an
indeterminate pointer value - undefined behaviour again.

Expression = An expression in C is any valid combination of operators,
constants and variables.

As I understand your reasoning it's not possible to have multiple
operators in the same expression, but that is not true. Or am I
misunderstanding you?

Why does dereferencing p-- work, e.g. *p-- works. But ++p-- doesn't
work.

You say that ++ only works on objects, what is your definition of an
object C.
Because & only works on objects, and the result of p-- is an expression, but
not an object.

Not sure I understan what you mean. What is your definition of an
object in C?
Because unary minus only works on arithmetic types, and int * is not an
arithmetic type.

Yes, thank you!
 
R

Richard Heathfield

dspfun said:
Richard Heathfield skrev:

Expression = An expression in C is any valid combination of operators,
constants and variables.

"An expression is a sequence of operators and operands that specifies
computation of a value, or that designates an object or a function, or that
generates side effects, or that performs a combination thereof."
As I understand your reasoning it's not possible to have multiple
operators in the same expression, but that is not true. Or am I
misunderstanding you?

b + c * d is a legal expression with two operators, so you are
misunderstanding me.
Why does dereferencing p-- work, e.g. *p-- works. But ++p-- doesn't
work.

Because ++ takes as its operand an object with scalar type. The expression
p-- yields a value, not an object.
You say that ++ only works on objects, what is your definition of an
object C.

I don't have one, but the C Standard says:

* Object --- a region of data storage in the execution environment,
the contents of which can represent values. Except for bit-fields,
objects are composed of contiguous sequences of one or more bytes, the
number, order, and encoding of which are either explicitly specified
or implementation-defined.
 
D

dspfun

Martin Ambuhl skrev:
Applying ++ to p-- is applying it to a value, not an object, but the ++
operator has a side effect which makes sense only when applied to a
modifiable object.

p-- is an expression. It is possible to do *p--. So we can use at least
some operators on expressions, that's probably why we have the
associativity rules.

I'm having problem understanding what you mean, probably because I
don't understand your definition of object and value?

In C I know of different types of variables, constants, pointers,
arrays and structs as well as enums.
i++ is not an object: it has no address for the & operator to return.

Same question as above, what is the defintion of an object?
[etc]


In general, is it good programming practice to make use of the
precedence and associativity of operators?

It is good programming practice to use operators only in contexts for
which they make sense. Why are writing arcane expressions like these
when you don't have a clue what the operators do?

That's the reason why I'm writing arcane expressions like this; to
grasp a solid understanding of what the operators are doing to its
operands and how the operators can be used.

Thanks for your response!!
 
D

dspfun

Because ++ takes as its operand an object with scalar type. The expression
p-- yields a value, not an object.

So why does it work when you split ++p-- in two different lines?:
p--;
++p;

You'll probably say the same thing again, but what is the main
difference when splitting the expression in two?

Thanks again for your response!!
 
J

John Bode

dspfun said:
Martin Ambuhl skrev:


p-- is an expression. It is possible to do *p--. So we can use at least
some operators on expressions, that's probably why we have the
associativity rules.

The ++ and -- operators require that their operands are lvalues;
lvalues are expressions that refer to a region of memory such that the
memory can be read or modified. In other words, ++ and -- only modify
objects in memory.

For example, given the following code fragment:

int i = 2;
int *p = &i;
int j = *p++;

The expressions i, j, and p are all lvalues; they all refer to a region
of memory that can be read and modified. The integer constant
expression 2 is not an lvalue. It does not refer to a region of
memory, so an expression like 2++ would not make sense, since there's
nothing to apply the side effect to.

The result of an autoincrement or autodecrement expression is a value,
not an lvalue; for example, given the code above, the result of the
expression --i would be 1, which is not an lvalue. So writing the
expression --i++ would be equivalent to writing 1++, which, again, is
nonsensical.

So what about expressions like *p++? The indirection operator doesn't
require that its operand be an lvalue, because it's not attempting to
modify a region of memory directly. Assuming that the value stored in
p is 0x8000, the result of the expression p++ is the *value* 0x8000,
and the dereference operator is applied to that value.
I'm having problem understanding what you mean, probably because I
don't understand your definition of object and value?

An object has a specific location in memory. A value doesn't.
In C I know of different types of variables, constants, pointers,
arrays and structs as well as enums.
i++ is not an object: it has no address for the & operator to return.

Same question as above, what is the defintion of an object?
[etc]


In general, is it good programming practice to make use of the
precedence and associativity of operators?

It is good programming practice to use operators only in contexts for
which they make sense. Why are writing arcane expressions like these
when you don't have a clue what the operators do?

That's the reason why I'm writing arcane expressions like this; to
grasp a solid understanding of what the operators are doing to its
operands and how the operators can be used.

Thanks for your response!!
 
R

Richard Heathfield

dspfun said:
So why does it work when you split ++p-- in two different lines?:
p--;
++p;

p-- decrements p, and yields as its result the old >>>*value*<<< of p, NOT
the p object itself. For example, if p points to an int at address
0x1234ABCD, then p-- yields the >>>*value*<<< 0x1234ABCD (and, as a side
effect, adjusts the value of p). ++0x1234ABCD makes no sense, so ++p--
doesn't make sense.

But ++p; means "yield as your result the new value of p and, as a side
effect, adjust the value of p". ++, in this case, is being applied to p,
whereas in the (illegal) case of ++p--, you are trying to apply ++ not to p
but to the value that results from evaluating the expression p--.
 
K

Keith Thompson

dspfun said:
So why does it work when you split ++p-- in two different lines?:
p--;
++p;

Please don't snip attribution lines (lines of the form
"So-and-so writes:").

In each of "p--;" and "++p;", the name "p" refers to an object.
The "--" and "++" operators modify an object.

Let's assume that "++p--" is grouped as "++(p--)". I'm not certain
that the grouping is even defined. Then in the subexpression "p--",
"p" refers to an object, but "p--" refers only to a value, the result
of the expression. The prefix "++" operator cannot legally be applied
to an expression that does not designate an object. (The term for an
expression that designates an object is "lvalue".)

If you wonder why a given expression is illegal, think about what it
would mean if it were legal. For example, the assignment operator "="
requires an lvalue as its left operand (that's where the term "lvalue"
comes from). This:
x + 1
is a legal expression, but not an lvalue. This:
x = 42;
is a legal assignment; "x" by itself is an lvalue. But this:
(x + 1) = 42;
is illegal, because "(x + 1)" is not an lvalue. Even if it were
legal, it wouldn't make any sense (except perhaps in a language with a
very different computational model than C's).
 
O

Old Wolf

Richard said:
dspfun said:

Because ++ only works on objects, and the result of p-- is an expression,

This is not correct. An expression is a sequence of operators etc.
p-- is an expression. Its result is not an expression; its result is
a value. The compile error is because the expression p-- is not an
lvalue expression.
but not an object.

The more I think about this statement, the more murky it seems.
Better to stick to "lvalue" !
No, it doesn't. Drop the second =.


The behaviour is undefined because p's value is changed to an invalid value.

Why do you accept 10101010 as a valid value, but not one less than that?
 
R

Richard Heathfield

Old Wolf said:
This is not correct. An expression is a sequence of operators etc.
p-- is an expression. Its result is not an expression; its result is
a value.

If you are right, then the Standard's grammar (which says it is a
postfix-expression) is wrong, and you'd better take that up with ISO.
Why do you accept 10101010 as a valid value, but not one less than that?

True, I should also have pointed out that the original assignment also
invokes undefined behaviour.
 
K

Keith Thompson

Richard Heathfield said:
Old Wolf said:

If you are right, then the Standard's grammar (which says it is a
postfix-expression) is wrong, and you'd better take that up with ISO.

p-- is an expression. The *result* of p-- is a value, not an expression.
 
R

Richard Heathfield

Keith Thompson said:
p-- is an expression. The *result* of p-- is a value, not an expression.

It's late. Or possibly early. My apologies to Old Wolf for incorrectly
rejecting his correction.
 
P

Peter Shaggy Haywood

Groovy hepcat dspfun was jivin' on 1 Jan 2007 14:02:50 -0800 in
comp.lang.c.
Associativity of unary C Operators's a cool scene! Dig it!

[Snip.]
Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

I would use other unary operators with sizeof when that is what I
need. For example, to get the size of something pointed to, I would
use the dereferenced pointer as the operand of sizeof. This is often
useful when dynamically allocating memory:

#include <stdlib.h>
some_type *ptr;
ptr = malloc(sizeof *ptr);

This is fine, even though the pointer has not been initialised before
calling malloc(). sizeof's operand is not evaluated, except to
determine the size, so the dereference is not actually performed.

--

Dig the even newer still, yet more improved, sig!

http://alphalink.com.au/~phaywood/
"Ain't I'm a dog?" - Ronny Self, Ain't I'm a Dog, written by G. Sherry & W. Walker.
I know it's not "technically correct" English; but since when was rock & roll "technically correct"?
 
D

dspfun

Peter "Shaggy" Haywood skrev:
Groovy hepcat dspfun was jivin' on 1 Jan 2007 14:02:50 -0800 in
comp.lang.c.
Associativity of unary C Operators's a cool scene! Dig it!

[Snip.]
Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

I would use other unary operators with sizeof when that is what I
need. For example, to get the size of something pointed to, I would
use the dereferenced pointer as the operand of sizeof. This is often
useful when dynamically allocating memory:

#include <stdlib.h>
some_type *ptr;
ptr = malloc(sizeof *ptr);

This is fine, even though the pointer has not been initialised before
calling malloc(). sizeof's operand is not evaluated, except to
determine the size, so the dereference is not actually performed.

void *p;
printf("%i\",sizeof(*p));

This prints 1, why is the size of a dereferenced void pointer 1?
 
D

dspfun

dspfun skrev:
Peter "Shaggy" Haywood skrev:
Groovy hepcat dspfun was jivin' on 1 Jan 2007 14:02:50 -0800 in
comp.lang.c.
Associativity of unary C Operators's a cool scene! Dig it!

[Snip.]
Another question, when would you use the unary operator sizeof with
other unary operators? To save code lines?

I would use other unary operators with sizeof when that is what I
need. For example, to get the size of something pointed to, I would
use the dereferenced pointer as the operand of sizeof. This is often
useful when dynamically allocating memory:

#include <stdlib.h>
some_type *ptr;
ptr = malloc(sizeof *ptr);

This is fine, even though the pointer has not been initialised before
calling malloc(). sizeof's operand is not evaluated, except to
determine the size, so the dereference is not actually performed.

void *p;
printf("%i\",sizeof(*p));

This prints 1, why is the size of a dereferenced void pointer 1?


printf("%i\n",sizeof(void));

This prints 1, so I guess the size of the void type is 1 byte.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Staff online

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,073
Latest member
DarinCeden

Latest Threads

Top