左值,右值,数组和指针

左值,右值,数组和指针

为什么a=b?

      在常见的C风格的语言中,有一些细微的差别是容易被忽视的,而正是这些看似非常简单的知识,有时候会成为我们理解程序,理解计算机行为的瓶颈。比如表达式:a=b;
      这样的表达式在大多数编程语言中都是合法的,它是一个简单的赋值表达式,那么它如何来表示赋值的意思呢?通俗的来说,为什么当你敲下a=b这样的几个字符并运行程序,执行这条语句后a的值会是赋值前b的值?这样的问题也许会令你感到奇怪,也许你会说这就是如此,这个式子告诉编译器将b的值赋给a。那么站在编译器的角度它又是怎样看待这个表达式的呢?
      要弄清这样的问题,有两个概念也许你需要温习一下,那就是“地址”和“地址的内容”。在编译器“眼里”,赋值运算符两边的a和b并不是两个地位平等的代号。这里的等号“=”并不代表a和b的地位平等;-),这里符号a的含义是a所代表的地址,习惯上我们称之为左值。而符号b的含义是b所代表的地址的内容,习惯上我们称之为右值,确切的说右值就是除了左值以外的值,他们可能是立即数和变量的某些组合。左值在编译时可知,表示存储结果的地方。通常只有允许修改的左值可以合法用作赋值运算符左边的操作数。所以尽管数组的名字表示“地址”,它是“左值”,但是它不是一个可以修改的左值,所以它也就不能用作赋值运算符的左操作数了,同理,用关键字const修饰的变量也是如此。编译器为每个变量分配一个地址(左值),虽然具体什么时候分配这个地址各个语言的编译器可能有所不同,但是变量在运行时会一直保存于这个地址。
      相对应的,存储于变量中的值(它的右值)只有在运行时才可知。如果需要用到变量中存储的值,编译器就发出指令从指定的地址读入变量值并将它存于寄存器中。也就是说,右值是运行时才会需要去用的实际值。于是"a=b;"的背后发生的事情大致就是,编译器会先检查操作数的合法性,如果操作数合法,就生成如下的指令:取b的右值(可能是一个立即数),将其保存至a的左值(一个地址)。

访问数组与指针的区别

      注意前面提到的a,b的地址(左值)都是编译器编译时已知的,无论是直接的地址,还是带偏移量的地址,编译的时候不需要再通过额外的指令去请求得到地址。这里也正是数组和指针不太一样的地方,以C语言为例:
char a[6]="Freesc";
c=a[i];
      经过编译,编译器符号表里面已经有了a的地址比如8888,运行时第一步是取i的值,将它与8888相加。第二步是取地址(8888+i)的内容。这正好可以解释为什么extern char a[]与extern char a[888]等价,这两个声明都提示编译器a是一个数组,对应的有一个地址,数组的元素可以从这个地址找到,编译器不需要知道数组总共有多长(好吧,我忽悠人的,其实有些编译器基于安全考虑会检查数组长度,这里以ANSI C编译器为例)。而指针的操作则不同,如果声明的是extern char *p,它告诉编译器p是一个指针,一个4个字节的对象(我又忽悠人了,我意思是常见的编译器,暂时不考虑过气的TC之流),它指向的对象是一个字符,它的内容(值)是那个字符的地址。此时编译器并不知道p的地址,为了得到指针对应的字符,必须先取得指针指向的地址,再根据这个地址来取得字符。因此指针的访问要比数组需要多一些额外的提取地址的操作,换句话说,必须先通过指针变量的左值(地址)取得指针指向对象的左值(地址),然后再取得该地址的内容。

思考题
      好了,现在不妨来思考一下这样一个问题,假如p被声明为指针:extern char *p;而它原来的定义是char p[6]那么当我们用p[i]来提取元素的时候会发生什么呢?
我的回答是(倒过来念):
掉挂序程的你让能可有这,址地标目的放存针指成当被会符字的应对p[i]
你的答案呢?如果原来的定义是char *p而声明成为extern char p[];又会发生什么呢?如何发生的?

参考资料
Peter van der Linden, Expert C Programming, Prentice Hall PTR (June 24, 1994)
Wiki, Value_(Computer Science), http://en.wikipedia.org/wiki/Value_(computer_science)

Freesc
2009年8月19日 
 

posted on 2009-08-19 13:03 J.D Huang 阅读(...) 评论(...) 编辑 收藏

统计