【问题标题】:Why is an lvalue required as left operand of assignment in this for-loop?为什么在这个 for 循环中需要左值作为赋值的左操作数?
【发布时间】:2020-04-05 04:38:39
【问题描述】:

这不是我的代码;我正在为 Linux 编译一个非常古老的互联网聊天/文件共享客户端和服务器 - 虽然服务器编译得很好,但我在编译客户端时遇到了错误。

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;
}

编译的时候报错:

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

但我不完全确定为什么。此错误也出现在其他文件的 for 循环中;所以它可能是旧的 C 样式,也许?我认为原始代码是在 2003 年编写的。我不确定。但我们将不胜感激任何解决此问题的帮助。

【问题讨论】:

  • 抱歉,使用 nano;它没有复制整行。
  • 你并没有真正问为什么它需要是左撇子,因为这很明显。根据定义,分配的任何左侧都必须是。我想你实际上是在问为什么它不被认为是左撇子。
  • 部分。如果我不能对代码的 LHS 进行投射;那么我该如何重组它以使其工作呢?将声明 char * fh = NULL;做吗?
  • 有点拗口,但类似:fh = (struct hl_filelist_hdr *)((char *)fh + (flen + SIZEOF_HL_DATA_HDR))

标签: c linux file for-loop


【解决方案1】:

赋值表达式必须有一个地方来存储被赋值的值。所以左操作数必须指定这样一个地方。这称为左值——左值是一个表达式,它指定一个对象,例如intdouble、另一种基本类型、结构和某些其他事物。

最常见的左值只是一个标识符——对象的名称。例如,在int x; 定义了一个对象(为x 保留的内存)和一个标识符(名称x)之后,x 指定了该对象。

诸如37'a' 之类的常量只是C 中的。它们不指定内存中的任何内容。大多数表达式产生简单的值,而不是左值。例如,3 * x 的结果是x 的值的三倍(如果没有溢出),它只是一个值,而不是左值,即使它使用了x。另一个例子是,(char *) p 的转换结果只是一个指针的值;它不是p 的左值。

其他左值包括:

  • 将一元 * 应用于指针的结果。如果p是一个有效值指向某个对象的指针,那么*p是该对象的左值,所以你可以写*p = 37;,假设对象类型与被赋值为37兼容。

  • 结构的成员。如果s 是具有成员foo 的结构,则s.foo 是该成员的左值。这也适用于指向结构的指针;如果p 指向s,则p-&gt;foo 是成员foo 的左值。 (请注意,s 本身必须是左值。可能有一个只是一个值的临时结构。在这种情况下,对其成员的引用只是一个值,而不是左值。)

在赋值(char *)fh += flen + SIZEOF_HL_DATA_HDR 中,(char *)fh 未被 C 标准定义为左值。如果此代码被某些 C 编译器接受,则该编译器会为 C 语言提供一些不寻常的扩展。

看来此语句的意图是将flen + SIZEOF_HL_DATA_HDR 字节添加到fh 指向的位置。如果是这样,这可以通过将fh转换为char *,添加所需的数量,转换回fh的类型,然后将结果分配给fh来完成:

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

(在进行这种原始指针运算时存在一定的风险。这个答案与那些无关;我们假设底层代码处理这些,并且设计用于支持它正在做的事情的实现。)

【讨论】:

    【解决方案2】:

    好吧,表达式(char *)fh 不是您的编译器的左值。该行不正确:

    (char *)fh += flen + SIZEOF_HL_DATA_HDR

    我通过这种方式纠正它:

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

    C 中赋值运算符的语法如下:

    lvalue = rvalue;

    左值(定位器值)表示一个对象,它占据了内存中某个可识别的位置或(很少)内存本身。我会提到一些现代 C 语言中左值的例子:

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

    • 一个变量(例如:a = 0;
    • 一个结构(例如:structa = structb;
    • 数组的成员(例如:b[0] = 3;
    • 指向变量、结构、数组或数组成员的指针(例如:*c = 4;
    • 指向引用的指针(例如:*&amp;c = &amp;a;
    • 指向另一个指针的指针(例如:**d = 5;
    • 左值周围的组运算符(例如:(a) = 6;
    • 宏替换左值(例如:X = 7;
    • 常量左值是未经赋值许可的左值(例如:表达式e = 8;是错误的)

    左值通常不是:

    • 一个数字(例如:9 = a;
    • 任何数学表达式(例如:a + 10 = 11;

    在您的情况下,转换运算符不会返回左值进行赋值。

    【讨论】:

    • 关于第二个要点(编辑后的第四个):指针类型的表达式可能是也可能不是左值(例如,&amp;x 不是)。指针类型的变量将位于第一个项目符号下方(指针变量是变量),因此您可以删除该点。
    • “任何其他返回左值的运算符”可以改进:仅[](数组下标)、*(取消引用)和围绕左值分组括号。
    • 那个编辑不是很清楚...*&amp; 被定义为没有效果,c 是一个指向 int 的指针。也许您的意思是在您编写“指针”的所有情况下都说“取消引用指针”?
    • 我同意 *& 没有任何直接影响,但它是一个正确的左值。在带有宏的大型项目中了解它是件好事。
    【解决方案3】:

    由于我花了一些时间进行清理,因此我将在此处发布我重新编写的代码。恕我直言,代码早于 2003 年,它使用 alloca-constructs 和“struct hack”。可能魔法巫毒代码是从其他网络游戏程序中复制而来的,从未重做过。它可能起源于九十年代。


            /* 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?
    }
    

    【讨论】:

      【解决方案4】:

      在这里:https://stackoverflow.com/a/5367481/7332147 您或许可以找到比将 fh 声明为 char* 更好的解决方案。

      那个回答说

      (int *)p++;
      

      被拒绝,但是

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

      不是。

      在这个问题下的评论中:Casting a pointer does not produce an lvalue. Why? 写着:

      我不明白您为什么需要强制转换左值。在一个 赋值,你可以转换被赋值的右值。

      我认为这是另一个合理的解决方案,如您问题下的第 5 条评论中所述。

      不管怎样,以上两个链接都能让你更好地理解问题所在;乍一看,对我来说,你的演员应该可以工作,而且我相信我也使用类似的东西 - 但我通常使用非标准编译器,通常我不需要可移植性。

      【讨论】:

      • 您建议的修复是严格的别名违规
      • @M.M 考虑到整个情况,你的评论对我来说似乎有点太严厉了;无论如何,我提出了 两个 解决方案,都取自周围的其他答案,第二个是“干净的”,基本上说明了这里其他答案所说的内容,但其效果与第一个完全相同。跨度>
      • 您的答案中只提出了一个修复方案。如果您指的是第一句话中的链接,那也是严格的别名违规。或者“你可以转换被分配的右值”,这将与作者的意图有不同的行为。您所做的就是用运行时未定义的行为替换编译错误。
      • 读完后:stackoverflow.com/questions/98650/… 我想你不明白严格别名到底是什么,@M.M
      • 我对你也有同样的感觉。顺便说一句,考虑直接阅读标准
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-12-03
      • 2021-05-23
      • 2011-09-03
      相关资源
      最近更新 更多