在C语言中赋值运算符有啥作用?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在C语言中赋值运算符有啥作用?相关的知识,希望对你有一定的参考价值。

赋值符号"="
就是赋值运算符,作用是将一个数据赋给一个变量或将一个变量的值赋给另一个变量,由赋值运算符组成的表达式称为赋值表达式。一般形式为:
变量名=表达式
在程序中可以多次给一个变量赋值,每赋一次值,与它相应的存储单元中的数据就被更新一次,内存中当前的数据就是最后一次所赋值的那个数据。
例:a=12;此表达式读作
将10的值赋值给变量a。
说明:a、如果赋值号两边的运算对象类型不一致,系统会自动进行类型转换,转换的规则:将赋值号右边表达式的值的类型转换成赋值号左边变量的类型,
例:int y=3.5;在变量y中最终存储的是整数3。
b、可以将复制表达式的值再赋值给变量,形成连续赋值。
例如:x=y=25
是一个连续赋值表达式,
x=y=25等价于x=(y=25),所以表达式
x=y=25
最终的值为25。
参考技术A 赋值语句的作用是把某个常量或变量或表达式的值赋值给另一个变量。符号为'=。这里并不是等于的意思,只是赋值,等于用'=='表示。
注意:赋值语句左边的变量在程序的其他地方必须要声明。
得已赋值的变量我们称为左值,因为它们出现在赋值语句的左边;产生值的表达式我们称为右值,因为她它们出现在赋值语句的右边。常数只能作为右值。
例如: count=5; total1=total2=0;
第一个赋值语句大家都能理解。
第二个赋值语句的意思是把0同时赋值给两个变量。这是因为赋值语句是从右向左运算的,也就是说从右端开始计算。这样它先total2=0;然后 total1=total2;那么我们这样行不行呢?(total1=total2)=0;
这样是不可以的,因为先要算括号里面的,这时 total1=total2是一个表达式,而赋值语句的左边是不允许表达式存在的。本回答被提问者采纳

在 C 中评估赋值运算符的左操作数有啥意义?

【中文标题】在 C 中评估赋值运算符的左操作数有啥意义?【英文标题】:What's the point of evaluating left operand of assignment operator in C?在 C 中评估赋值运算符的左操作数有什么意义? 【发布时间】:2016-12-27 08:25:19 【问题描述】:

根据 ISO C11 - 6.5.16.3,它说

    赋值运算符将值存储在由指定的对象中 左操作数。赋值表达式具有左边的值 赋值后的操作数,但不是左值。一个类型 赋值表达式是左操作数之后的类型 左值转换。更新存储值的副作用 左操作数在左的值计算之后排序,并且 右操作数。操作数的计算是无序的。

所以我猜这意味着,例如,

int x = 10;
x = 5 + 10;
    左操作数x被评估为10,右操作数被评估为15。 右操作数值存储在左操作数x指定的对象中。

但是,如果赋值的目的是存储右操作数的评估值(就像在步骤 2 中一样),为什么需要评估左操作数?评估左操作数有什么意义?

【问题讨论】:

它在哪里说左操作数被评估? "左边的值计算后..." 【参考方案1】:

x 被评估为lvalue 时,它不会评估为10。它会评估为lvalue,其中可以存储RHS 的值。如果 LHS 未计算为 lvalue,则该语句将出错。

来自 C99 标准 (6.3.2.1/1):

lvalue 是一个表达式(对象类型不是 void),它可能指定一个对象;如果 lvalue 在评估时未指定对象,则行为未定义。

当您有一个简单的变量时,将 LHS 评估为 左值 是微不足道的,例如

 x = 10;

但是,它可能更复杂。

 double array[10];
 int getIndex();   // Some function that can return an index based
                   // on other data and logic.

 array[getIndex()+1] = 10.0;

 // This looks like a function call that returns a value.
 // But, it still evaluates to a "storage area".
 int *getIndex2()  return(&array[0]); 
 *getIndex2()=123.45; // array[0]=123.45

如果 getIndex() 返回 5,则 LHS 求值为指定数组的第 7 个元素的 左值

【讨论】:

你能详细解释一下被评估为左值是什么意思吗? 这是否意味着array[getIndex()+1] 没有被评估为某个指针值,而是被评估为某个指向 int 的指针类型表达式? 哇我从来没有听说过这样的概念,评估一个对象。您知道有关该主题的任何好的参考资料吗? 任何关于 C 的书都应该在讨论数组时解释这一点。如果您可以访问 C99 标准,则可以在第 6.5.2.1/2 节中找到:后缀表达式后跟方括号中的表达式 [] 是数组对象元素的下标指定。 作为另一个例子,你可以拥有*f() = 10;,这是一个 LHS 上的函数调用,它返回一个指针,然后解除引用产生一个要分配的对象。【参考方案2】:

在您的示例中,“左操作数”可能比您简单的 x 复杂得多(诚然,评估并不是一个真正的挑战):

*(((unsigned long*)target)++) = longValue;

肯定需要对 LHS 进行一些评估。您引用的句子是指在作业的左侧需要做什么才能找到正确的 lvalue 来接收作业。

【讨论】:

【参考方案3】:

只是为了从“犹大”的角度说服自己(如果尚未完成),这证明我的帖子只回答了您简单案例中的简单问题。

小证据表明,在您的简单示例中 gcc 只做它需要的,而不是更多:

代码:

int main()

int x = 10;
x = 5 + 10;

return x;

使用调试构建

K:\jff\data\python\***\c>gcc -g -std=c11 -c assign.c

带有混合 C/asm 代码的 objdump

K:\jff\data\python\***\c>objdump -d -S assign.o

assign.o:     file format pe-x86-64


Disassembly of section .text:

0000000000000000 <main>:
int main()

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 30             sub    $0x30,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
int x = 10;
   d:   c7 45 fc 0a 00 00 00    movl   $0xa,-0x4(%rbp)
x = 5 + 10;
  14:   c7 45 fc 0f 00 00 00    movl   $0xf,-0x4(%rbp)

return x;
  1b:   8b 45 fc                mov    -0x4(%rbp),%eax

  1e:   90                      nop
  1f:   48 83 c4 30             add    $0x30,%rsp
  23:   5d                      pop    %rbp
  24:   c3                      retq
  25:   90                      nop
  26:   90                      nop
  27:   90                      nop
  28:   90                      nop
  29:   90                      nop
  2a:   90                      nop
  2b:   90                      nop
  2c:   90                      nop
  2d:   90                      nop
  2e:   90                      nop
  2f:   90                      nop

正如其他(不错的)答案中所述,不愿意解释,但如果表达式更复杂,则必须计算存储值的地址,因此需要进行某种评估。

编辑:

使用一些稍微复杂的代码:

int main()

int x[3];
int i = 2;
x[i] = 5 + 10;

return x[i];

反汇编:

Disassembly of section .text:

0000000000000000 <main>:
int main()

   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 30             sub    $0x30,%rsp
   8:   e8 00 00 00 00          callq  d <main+0xd>
int x[3];
int i = 2;
   d:   c7 45 fc 02 00 00 00    movl   $0x2,-0x4(%rbp)
x[i] = 5 + 10;
  14:   8b 45 fc                mov    -0x4(%rbp),%eax  <== hey, could be more optimized here: movl   $0x2,%eax covers line+above line :)
  17:   48 98                   cltq
  19:   c7 44 85 f0 0f 00 00    movl   $0xf,-0x10(%rbp,%rax,4)  <== this line holds the left-operand evaluation, in a way, %rax is used to offset the array address
  20:   00

return x[i];
  21:   8b 45 fc                mov    -0x4(%rbp),%eax
  24:   48 98                   cltq
  26:   8b 44 85 f0             mov    -0x10(%rbp,%rax,4),%eax

  2a:   90                      nop
  2b:   48 83 c4 30             add    $0x30,%rsp
  2f:   5d                      pop    %rbp
  30:   c3                      retq

【讨论】:

您标记的行 x = 10 与 LHS 的评估并不完全一致 - 您标记的内容指的是作业 int x = 10。 LHS 在这里只是-4(rbp),因此评估起来非常简单,我们看不到评估。 你可能是对的。所以为了结束辩论,我用直接混合 C/asm 输出改进了这篇文章。编译器是对的:) 您可以通过使作业的 LHS 更复杂一些来真正加强您的答案,以便我们实际上可以看到更多的评估。 回答加强了。谢谢。我对赞成票感到荣幸,即使它展示了它是如何工作的,而不是其他答案给出的真正解释。 @OP:不要接受那个:) 查看未优化的反汇编确实是一个毫无意义的练习。代码更难阅读,而且根本不是真实世界。【参考方案4】:

= 左侧有非平凡的表达式,需要一直计算。以下是一些示例。

int array[5];
int *ptr = malloc(sizeof(int) * 5);

*ptr = 1;      // The lhs needs to evaluate an indirection expression
array[0] = 5;  // The lhs needs to evaluate an array subscript expression

for (int i = 0; i < 5; ++i) 
    *ptr++ = array[i];  // Both indirection and postincrement on the lhs!


// Here, we want to select which array element to assign to!
int test = (array[4] == 0);
(test ? array[0] : array[1]) = 5; // Both conditional and subscripting!

【讨论】:

【参考方案5】:

不然怎么办

int x, y, z;
x = y = z = 5;

工作? (赋值“z=5”必须将z的(r-)值赋予赋值“y= ...”,然后必须将y的值赋予赋值“x= ...”。 )

幕后行为是:

    将值 5 加载到寄存器中(在下面的第 7 步之前不要将此寄存器重复用于其他任何操作) 将z的地址加载到寄存器中(这是“z”用作左值时的含义。) 地址为z的店铺5。 5 现在是“z”的右值。请记住,CPU 使用值和地址,而不是“z”。变量标签“z”是对包含值的内存地址的人类友好引用。根据它的使用方式,我们要么想要它的值(当我们获取 z 的值时),要么想要它的地址(当我们替换 z 的值时)。 在寄存器中加载y的地址。 将z (5) 的值存储在y 的地址。 (应该/可以优化和重用第一步中的“5”。) 将x的地址加载到寄存器中。 将y (5) 的值存储在x 的地址。

【讨论】:

赋值链之所以有效,是因为赋值是 C 中的表达式(不是语句),例如,z = 5 是一个计算结果为 5 的表达式。(它还具有将值存储在 z 中的副作用.) 但这确实与OP的问题无关。 @PaulJ.Lucas :您似乎不想回答 OP 的问题,“评估左操作数有什么意义?”,他从标准语言“赋值表达式有赋值后左操作数的值......”正如你正确指出的那样,答案就是我写的。 我的评论不适用于 R Sahu 已经回答其问题的 OP。我的评论是对您的“答案”的回应,正如我所指出的,这与 OP 的问题或真实答案无关。 @EricTowers 您的赋值链示例涉及重复评估赋值的右侧。 OP 的问题是关于评估 left 手边。 @jamesdlin:不正确。您的声明会在最右边的子表达式中重复副作用。这与 OP 引用的文本相矛盾:“......在赋值后具有左操作数的值”。试试看:x=y=z++;z 增加了多少次?对于评估中的x=((temp)) 步骤,我们计算的右值不是z++,而是新的y(或者我们从嵌套赋值中记住它),但是y 的外部评估是不完整的,直到它强制为可分配给x 的类型。这都是关于评估左侧。

以上是关于在C语言中赋值运算符有啥作用?的主要内容,如果未能解决你的问题,请参考以下文章

C++中赋值运算操作符和=重载有啥区别?

C语言里面的点.运算符有啥作用表示啥还有下划线

C语言中的一个等号和两个等号有啥区别

+=和=+ C赋值运算符有啥区别[重复]

C语言中位移位运算符?

c语言中规定 赋值运算符的左边必须是啥?