1

This isn't my code; I'm working on getting a very old internet chat/file sharing client and server compiled for Linux - and while the server compiles fine, I'm running into errors compiling the client.

fh_lookup (const char *path)
{
        struct hl_filelist_hdr *fh;
        char const *p, *ent;
        char *dirpath;
        int len, flen, blen = 0;
        u_int16_t fnlen;
        struct cached_filelist *cfl;

        ent = path;
        len = strlen(path);
        for (p = path + len - 1; p >= path; p--) {
                if (*p == dir_char) {
                        ent = p+1;
                        while (p > path && *p == dir_char)
                                p--;
                        blen = (p+1) - path;
                        break;
                }
        }

        dirpath = xmalloc(blen + 1);
        memcpy(dirpath, path, blen);
        dirpath[blen] = 0;

        for (cfl = cfl_list->next; cfl; cfl = cfl->next)
                if (!strcmp(cfl->path, dirpath))
                        break;
        xfree(dirpath);
       if (!cfl)
                return 0;

        for (fh = cfl->fh; (u_int32_t)((char *)fh - (char *)cfl->fh) <
cfl->fhlen; 
            (char *)fh += flen + SIZEOF_HL_DATA_HDR) {
                L16NTOH(flen, &fh->len);
                L16NTOH(fnlen, &fh->fnlen);
                if (!memcmp(fh->fname, ent, fnlen))
                        return fh;
        }


        return fh;
}

When compiling, I get the error:

error: lvalue required as left operand of assignment
             (char *)fh += flen + SIZEOF_HL_DATA_HDR) {

But I'm not entirely sure why. This error appears in for-loops throughout other files as well; So it might be old C styling, perhaps? I think the original code was written in 2003. I'm not sure. But any help to fix this would be greatly appreciated.

OrangeCalx01
  • 806
  • 2
  • 7
  • 21
  • Sorry, using nano; It didn't copy the whole line. – OrangeCalx01 Apr 05 '20 at 04:45
  • 1
    You are not really asking why it needs to be lefthand, because that is obvious. Any lefthand side of an assignment has to be, by definition. I think you are actually asking why it is not considered a lefthand. – Yunnosch Apr 05 '20 at 04:48
  • 1
    Does this answer your question? [lvalue required as left operand of assignment in some old c code](https://stackoverflow.com/questions/20581707/lvalue-required-as-left-operand-of-assignment-in-some-old-c-code) – kaylum Apr 05 '20 at 04:49
  • Partially. If I can't cast on the LHS of the code; Then how could I restructure it to make this work? Would declaring char * fh = NULL; do it? – OrangeCalx01 Apr 05 '20 at 05:30
  • 1
    A bit of a mouthful but something like: `fh = (struct hl_filelist_hdr *)((char *)fh + (flen + SIZEOF_HL_DATA_HDR))` – kaylum Apr 05 '20 at 06:12
  • This code can be simplified a lot by indexing character arrays (strings) instead of juggling pointers. (probably) all the casts would disappear. – wildplasser Apr 05 '20 at 09:31
  • BTW: if the OP adds the struct definitions and the MACROs tot the question, I am willing to create a simplified version. – wildplasser Apr 05 '20 at 10:03
  • if the `struct cached_filelist *cfl` is properly aligned, you wont need any casts. If it is not, the code would invoke unaligned memory access. – wildplasser Apr 05 '20 at 10:26
  • Also, the return value is wrong if the final loop does not find a matching entry, a pointer beyond `((char*)cfh) + cfl->fhlen` is returned. It should probably return NULL instead. (unless the function returns an absolute memory adress (as a char pointer) , which is what I fear it does) The logic inside the final loop also looks wrong: it appears to change flen and fnlen. – wildplasser Apr 05 '20 at 10:48
  • The code presented does not conform to any version of the C standard. Presumably it worked with some C implementation at some time, else it would not be a surprise that a compiler rejects it now, but any compiler that accepted it was thereby providing an extension. – John Bollinger Apr 05 '20 at 12:41

4 Answers4

2

An assignment expression must have a place to store the value being assigned. So the left operand must designate such a place. This is called an lvalue—an lvalue is an expression that designates an object, such as an int, a double, another basic type, a structure, and certain other things.

The most common lvalue is simply an identifier—the name of an object. For example, after int x; defines an object (the memory reserved for x) and an identifier (the name x), x designates the object.

Constants such as 37 or 'a' are just values in C. They do not designate anything in memory. Most expressions produce simple values, not lvalues. For example, the result of 3 * x is three times the value of x (if there is no overflow), and it is just a value, not an lvalue, even though it used x. Another example is that, the result of the cast (char *) p is just the value of a pointer; it is not an lvalue for p.

Other lvalues include:

  • The results of applying unary * to pointers. If p is a pointer with a valid value pointing to some object, then *p is an lvalue for the object, so you can write *p = 37;, assuming the object type is compatible with being assigned 37.

  • Members of structures. If s is a structure with a member foo, then s.foo is an lvalue for that member. This applies to pointers to structures as well; if p points to s, then p->foo is an lvalue for the member foo. (Note that s must itself be an lvalue. It is possible to have a temporary structure that is just a value. In that case, a reference to its member is just a value, not an lvalue.)

In the assignment (char *)fh += flen + SIZEOF_HL_DATA_HDR, (char *)fh is not defined to be an lvalue by the C standard. If this code was accepted by some C compiler, that compiler was provided some unusual extension to the C language.

It appears the intent of this statement is to add flen + SIZEOF_HL_DATA_HDR bytes to the location pointed to by fh. If so, this can be accomplished by converting fh to char *, adding the desired amount, converting back to the type of fh, and then assigning the result to fh:

fh = (struct hl_filelist_hdr *) ((char *) fh + flen + SIZEOF_HL_DATA_HDR)

(There are certain hazards in doing this sort of raw pointer arithmetic. This answer does not speak to those; we assume the underlying code deals with those and is designed for implementation that support what it is doing.)

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

Well, the expression (char *)fh is not an lvalue for your compiler. The row is not correct:

(char *)fh += flen + SIZEOF_HL_DATA_HDR

I clould correct it by this way:

fh = (struct hl_filelist_hdr *)((char *)fh + flen + SIZEOF_HL_DATA_HDR))

We have following syntax for assignment operator in C:

lvalue = rvalue;

An lvalue (locator value) represents an object that occupies some identifiable location in memory or (rarely) memory itself. I will mention some examples of lvalue in modern C:

#define X a int a, b[2], *c, **d; const int e = -1;

  • a variable (e.g: a = 0;)
  • a structure (e.g: structa = structb;)
  • a member of array (e.g: b[0] = 3;)
  • a pointer to variable, structure, array or member of array (e.g: *c = 4;)
  • a pointer to reference (e.g: *&c = &a;)
  • a pointer to another pointer (e.g: **d = 5;)
  • a group operator around lvalue (e.g: (a) = 6;)
  • macros substituting lvalue (e.g: X = 7;)
  • a constant lvalue is an lvalue without permission of assignment (e.g: an expression e = 8; is wrong)

Lvalue is typically not:

  • a number (e.g: 9 = a;)
  • any math expression (e.g: a + 10 = 11;)

In your case, the conversion operator will not return lvalue for assignment.

  • Regarding the second bullet point (fourth after your edit): An expression of pointer type may or may not be an lvalue (e.g. `&x` is not) . A variable of pointer type would come under the first bullet point (pointer variables are variables) so you could delete that point. – M.M Apr 05 '20 at 09:45
  • "any other operator returning lvalue" could be improved: only `[]` (array subscript), `*` (dereference) and grouping parentheses around an lvalue . – M.M Apr 05 '20 at 09:50
  • that edit is not very clear ... `*&` is defined as having no effect , and `c` is a pointer to int . Maybe you mean to say "dereferencing a pointer" in all the cases where you wrote "a pointer" ? – M.M Apr 05 '20 at 11:44
  • 1
    I agree that *& does not any direct effect, but it is a correct lvalue. It is good to know it in large projects with macros. – Marian Lichner Apr 05 '20 at 12:29
0

Since I spent some time on cleaning up, I'll post my reworked code here. IMHO the code is older than 2003, it uses alloca-constructs, and "struct hack". Probably the magic voodoo code was copied from other networked game programs, and never reworked.It could originate from the nineties.


        /* GUESSED structure definitions */
struct hl_filelist_hdr {
        u_int16_t len;          //<< Total length, rounded up modulo 32bit (EXLUDING header??) ; in network byte order.
        u_int16_t fnlen;        //<< string length, presumably, including NUL-byte, rounded up modulo 32bit; in network byte order.
        char fname[1];          //<< "STRUCT HACK" construct???
        };


struct cached_filelist {
        struct cached_filelist  *next;
        unsigned fhlen;
        char *path;
        struct hl_filelist_hdr *fh;
        };

        /* appears to be global */
struct cached_filelist *cfl_list=NULL;

struct hl_filelist_hdr *fh_lookup (const char *path)
{
        struct hl_filelist_hdr *fh;
        struct cached_filelist *cfl;
        unsigned len, namelen, dlen = 0;
        unsigned nstart;

        len = strlen(path);
        if(!len) return NULL; // an empty path would not make sense

                // find final (back)slash
        nstart = 0;
        for (dlen=len ; dlen-- >0; ) {
                if (path[dlen] != dir_char) continue;
                // filename starts after the final slash
                nstart = dlen+1;
                // if the slash was doubled, dont include it in the dirpath.
                while (dlen  > 0 && path[dlen-1] == dir_char) {dlen--;}
                break;
        }
        namelen = len - nstart;
        if(!namelen) return NULL; // an empty name would not make sense

        for (cfl = cfl_list->next; cfl; cfl = cfl->next) {
                if (!memcmp(cfl->path, path, dlen) && !cfl->path[dlen]) break;
                }

                // Directory is not in cache: nothing we could do
       if (!cfl) return NULL;

        /** we dont need this anymore
        dirpath = xmalloc(dlen + 1);
        memcpy(dirpath, path, dlen);
        dirpath[dlen] = 0;
        **/

                /* Without the MACRO definitions, this cannot be
                ** rewritten.
                ** It looks like a "struct-hack" kind of thing, but aligned on 32bit boundaries
                */
        for (   fh = cfl->fh;
                (u_int32_t)((char *)fh - (char *)cfl->fh) < cfl->fhlen; // <<--
                (char *)fh += flen + SIZEOF_HL_DATA_HDR //<<--
                ) {
                u_int16_t fnlen;`               //<<
                L16NTOH(flen, &fh->len);        //<<-- len appears to be a rounded-up length of the variable-size struct-array element, in bytes.
                L16NTOH(fnlen, &fh->fnlen);     //<<--
                if (!memcmp(fh->fname, path+nstart, fnlen)) // fnlen appears to include the NUL-byte
                        return fh; // Found it!
        }

        return NULL; // not cached
        // return fh; <-- would point beyond the allocated size. Is this intended?
}
wildplasser
  • 43,142
  • 8
  • 66
  • 109
-1

Here: https://stackoverflow.com/a/5367481/7332147 you can perhaps find a better solution than declaring fh as a char*.

That answer says that

(int *)p++;

is rejected, but

(*(int**)&p)++;

is not.

In a comment under this question: Casting a pointer does not produce an lvalue. Why? there is written:

I don't see why you'd have any need to cast an lvalue. In an assignment, you can cast the rvalue being assigned.

This is another reasonable solution, I think, as reported in the 5th comment under your question.

Anyway, both the links above can make you better understand what the problem is; at first sight, for me your cast should work, and I believe I also use similar things - but I normally use non-standard compilers and normally I don't need portability.

  • Your suggested fix is a strict aliasing violation – M.M Apr 05 '20 at 09:42
  • @M.M considering the whole situation, your comment seems a little too severe to me; anyway I proposed *two* solutions, both taken from other answers around, and the second one is "clean" and says essentially what other answers here say, but its effect is exactly the same as the first. – linuxfan says Reinstate Monica Apr 05 '20 at 16:31
  • There's only one fix proposed in your answer. If you mean the link in your first sentence, that is also a strict alising violation. Or "you can cast the rvalue being assigned", that will have different behaviour to what the author intended. All you do is replace a compilation error with runtime undefined behaviour . – M.M Apr 05 '20 at 23:23
  • After reading this: https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule I think you don't understand what strict aliasing exactly is, @M.M – linuxfan says Reinstate Monica Apr 06 '20 at 05:13
  • I feel the same way about you. BTW consider reading the Standard directly – M.M Apr 06 '20 at 05:17
  • @M.M "Part first. What is aliasing exactly? Aliasing is when more than one lvalue refers to the same memory location". How many lvalues do we have in the context of this question? – linuxfan says Reinstate Monica Apr 06 '20 at 05:50