Path: utzoo!mnetor!uunet!husc6!purdue!umd5!mimsy!chris From: chris@mimsy.UUCP (Chris Torek) Newsgroups: comp.lang.c Subject: Re: lvalues (was Re: Question about type casting) Message-ID: <10236@mimsy.UUCP> Date: 18 Jan 88 18:43:44 GMT References: <546@xyzzy.UUCP> <5080012@hpfcdc.HP.COM> <10227@mimsy.UUCP> <2975@killer.UUCP> Organization: U of Maryland, Dept. of Computer Science, Coll. Pk., MD 20742 Lines: 104 >In article <10227@mimsy.UUCP> I claimed taht >>>int i; char *c; >>>c = (char *) &i; >>>* (int *) c = 0; >is not portable, but it is legal. In article <2975@killer.UUCP> jfh@killer.UUCP (The Beach Bum) writes: >I believe it used to be portable. It used to be `portable by default': that is, there was no portable way to do it at all, but that came closest. >Now that X3J11 has mangled the language, it isn't guaranteed to be >portable. Doug Gwyn mentioned that the current draft makes it portable again. (My interpretation of his remark; beware.) >>The result of a cast is an `rvalue' .... >>Assignments may be made only to `lvalues' .... The C indirection >>operator `*' takes an rvalue and yeilds an lvalue .... >Chris, that's some good voodoo. ? >A cast takes an object of whatever type it happens to be, and whatever >state of (l|r)value-ness and changes it's type, Good so far. Note that any lvalue can be converted to an rvalue. Note also that the cast may change its bit-pattern and/or shape (e.g., casting short to double). >but not it's (l|r)value-ness. Whoa! Stop! No! The result of a cast is effectively that of assigning to an unnamed temporary variable with the type given in the cast. This is an rvalue. >The difference between lvalue expressions and rvalue expressions is >more obvious in a language such as BLISS with all those silly dots. . . . No: If I understand correctly, in BLISS, every expression can be veiwed as both an lvalue *and* an rvalue. This is untrue in C. >The only restriction on what can be on the left hand side of an `=' is >having an address and a type, or being finaglable into having both. No: it must be an lvalue. Naturally, all lvalues have a type; most even have addresses, although some (register) might not. But this is not the abstraction defined by the C language. [**] >I can cast an obvious rvalue like `2' into an int * with ((int *) 2). Yet it is still an rvalue. >This thing can now appear on either side - > > *((short *) 2) = *((short *) 4); (There are no `int *'s above.) Once again, `*' (indirection) takes an rvalue and produces an lvalue. *(short *)2, if it means anything, means an lvalue at 2 (whatever that is). If `2' happens to be a valid address for a variable of type `short', this goes; if not, you get a segmentation fault or other weird error. If I had my K&R here, I would check, but I believe the only operators that convert rvalues to lvalues are unary `*' (indirect, not multiply) and `->' (pointer-to-structure-member). All others result in rvalues; some, such as unary `&' (address of) work only on lvalues. Mathematically speaking, unary `&' and `*' are inverse functions. Unary `&' takes an lvalue of type `T' and produces an rvalue of type `pointer to T'; unary `*' takes an rvalue of type `pointer to T' and produces an lvalue of type `T'. The pairs from unary `&' must be distinct for distinct lvalues [*], and there may be only one value produced for any particular lvalue. This makes it an invertible function; `*' is then defined as the inverse function. While `*' is a well-behaved function on conventional architectures, all that the language requires is that it be the inverse of `&'. ----- [*] Actually, pairs need not be distinct for `dead' lvalues, e.g., `int *i1,*i2; { int j1; i1 = &j1; } { int j2; i2 = &j2; }'. The language states that using the pair of a dead lvalue is an error. Whether certain different-looking lvalues are in fact the same on segmented architectures with built-in aliasing (80x86) is open to debate, but there is no legal way to obtain two different pairs that yeild the same address. That is, it is easy to do, but it is not part of the C language itself. [**] The strongest statements the C language makes about addresses are: they exist; they have values of type `pointer to T'; some form of arithmetic, with the two operations A + I => A and A - A -> I, works on them; the legal range for the integer value I is defined by the declaration (array) or allocation (via malloc) of the object; adding a legal integer I to an address A yeilds an address A1 such that A1 - A = I; successively adding 1 to an address yeilds successive array elements. (I think that covers it: the rest can be derived.) -- In-Real-Life: Chris Torek, Univ of MD Comp Sci Dept (+1 301 454 7163) Domain: chris@mimsy.umd.edu Path: uunet!mimsy!chris