C语言中那些与顺序有关的内容汇集--持续更新中

  这篇文章汇集C语言中与顺序有关的内容,包括存储顺序、运算顺序等,持续更新,由于小弟能力有限,恳请所有看到这篇拙文的广大大佬们,积极指正,在此谢过了。


1、运算符的优先级 - 运算符与运算符之间的优先顺序

说起运算顺序,就不得不谈运算符的优先级,这里,祭上《C陷阱与缺陷》一书中给出的C语言运算符优先级表

                                       

记忆原则为:把该优先级表中的运算符分为三类:

第一等:特权派,严格来说它们根本不是运算符,但优先级是最高的,当然地位是同等的,并且都是自左向右结合,包括:数组下表、函数调用操作符——即开括号()、结构成员选择操作符;

第二类:单目运算符:这里特别要注意的是,它们是自右至左结合的!!!因此,*p++会被编译器解释成*(p++),即取指针p所指向的对象,然后将p递增1;

第三类:多目运算符:这里还有小类:算术运算符>(移位运算符)>关系运算符(带大小于号的要优于 == 和 !=)>逻辑运算符(包括按位运算符和逻辑运算符)>(条件运算符>赋值运算符>逗号运算符)

  注意:

int i,j,k;
i=j=k=0; <<=>>k=0;j=k;i=j;


2、求值顺序 - 给定一个运算符,操作数与操作数之间的取值顺序

C语言中,只有四个运算符存在着规定的求值顺序,分别为:&&||?:C语言中其他所有运算符对其操作数求值的顺序都是未定义的,运算符取值顺序依具体编译器而定.特别的,赋值运算符并不保证任何求职顺序

例如:要对a<b求值,编译器可能可能先对a求值,也有可能先对b求值,甚至有的编译器同时对a和b同时求值。

       需要注意的是:对于&&||?:,这四种运算符:

       a)运算符&&和运算符||先对左侧操作数求值,只在需要时才对右侧操作数求值。

例如:

     int a=0,b=1;


     if(b++||(a=b))
     {
         printf("a:%d,b:%d",a,b);
     }

输出是:a=0,b=2。

      b)条件运算符?:有三个操作数a?b:c,先求操作数a的值,根据a的值再去求b或c的值。

      c)逗号运算符,这里需要注意的是:分隔函数参数的逗号并非逗号运算符。例如x和y在函数f(x,y)中的求值顺序是未定义的,而在函数g((x,y))中却确定了先求x后求y的值得顺序。在后一个例子中,函数g只有一个参数,这个参数的值是这样求得的:先对x求值,然后x的值被“丢弃”,接着求y的值。-----《c陷阱与缺陷》


另外,对于函数参数的求值顺序和压栈顺序,二者不是一回事。压栈顺序是从右到左,而求值顺序则是undefined的,由编译器实现决定。下面以win7 gcc编译器为例:

 void f(int i,int j,int k)
 {
     int l;
     int g;
     printf("i:%d at [%x]\n",i,&i);
     printf("j:%d at [%x]\n",j,&j);
     printf("k:%d at [%x]\n",k,&k);
     printf("l:%d at [%x]\n",l,&l);
     printf("g:%d at [%x]",g,&g);
 }

void main()
{
    int i = 0;
    f(i++,i++,i++);
    return 0;
}

输出为:




这里,调用函数f时,压栈顺序依次为:k->j->i->l->g(即自右至左压栈);而计算顺序为:k->j->i(也为自右至左)。


需要注意是,程序的堆栈是由处理器直接支持的。在intel x86的系统中,堆栈在内存中是从高地址向低地址扩展(这和自定义的堆栈从低地址向高地址扩展不同),如下图所示:

image

    因此,栈顶地址是不断减小的,越后入栈的数据,所处的地址也就越低。


3、存储顺序 - 大小端

对于跨越多字节的程序对象,我们在存储时必须建立两个规则:这个对象的地址是什么,以及在存储器中如何排列这些字节——《深入理解计算机系统》。

a)第一个规则:几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。

例如,假设一个类型为int型的变量x,地址为0x100。也就是说,表达式&x的值为0x100。那么,x的四个字节将被存储在存储器的0x100,0x101,0x102和0x103的位置。

b)第二个规则:排列表示一个对象的字节存在着两种通用的规制。考虑一个w位(即用位表示时,有效位数为w)的整数,位表示为[Xw-1,Xw-2,...,X1,X0]。假设w为8的倍数,则这些位就能被分组为字节,其中最高有效字节包含位[Xw-1,Xw-8],最低有效字节包含位[X7,X6,...,X0],其他字节包含中间的位。

某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器选择从最高有效字节到最低有效字节的顺序存储。前一种规则——最低有效字节排在前面的方式,称为小端法(little endian),即低位优先;后一种规则——最高有效字节在前面的方式,称为大端法(big endian),即高位优先;(endian)

注意,不管是大端法还是小端法,一个对象的字节存储时,内存都是从低位地址向高位地址按顺序存放的。即:

大端法——最低地址存放高位字节,可称为高位优先,内存从最低地址开始,按顺序存放;

小端法——最低地址存放低位字节,可称为低位优先,内存从最低位地址开始,顺序存储。

这里说的最低位,指的是程序对象的地址,即第一个规则举例中,变量x的地址0x100。

举例说明:假设一个程序在一个源文件(file1.c)中包含声明:——来自《C陷阱与曲缺陷》,P81

long foo;

而在另一个源文件(file2.c)中包含了:

extern short foo;

又进一步假设,如果给long类型的foo赋一个较小的值,例如37,那么short类型的foo就同时获得了一个值37.我们能够对运行该程序的硬件做出什么样的推断?如果short类型的foo得到的值不是37而是0,我们又能够做出什么样的的推断?

《C陷阱与缺陷》给出了如下分析:如果把37赋值给了long型的foo,而short型的foo也得到值37,说明short型的foo与long型的foo包含了值37的有效位的部分,两者在内存中占用的是同一块区域。可能性有二:(1)long型和short型被实现为同一类型,但很少有C语言实现会这样;(2)更有可能的是,long型的foo低位部分与short型的foo共享了相同的内存空间,一般情况下,这个部分所处的内存地址较低。因此我们的一个可能推论就是,运行该程序的硬件是一个低位优先(little-endian)的机器。同样的道理,若short型的foo的值是0是,我们所用的硬件可能是一个高位优先(big-endian)的机器。

我觉得没有这么麻烦,file1.c中定义了long型的foo,那么在内存中就分配了一块sizeof(long)字节的内存,(32位操作系统下,long型占4字节,short型占2字节)(short型占字节数不得大于int型占字节数,int型占字节数不大于long型占字节数),并且取名foo,注意,这块内存与名字foo生死相依啊,同生共死的。file2.c中声明short型foo,注意,这里声明只是告诉编译器,foo已经在其他地方定义过了,file2.c引用foo时,首先找到long型foo的地址&foo,然后一起取sizeof(short)个字节作为short型foo对应的内存。如果所使用的机器是little-endian的,那么short型foo的值就是37,如果所使用的机器是big-endian的,那么short型的foo的值就是0。

4、for循环

首先,考虑下面这段代码输出是什么。

#include <stdio.h>

int main()
{
    int i = 0;

    for(i=0,printf("first i=%d\n",i);
    printf("Second,i=%d\n",i),i<10;
    i++,printf("Third,i=%d\n",i))
    {
        printf("Fourth,i=%d\n",i);
    }

    return 0;
}
这段代码是检测for循环运算逻辑的一个经典例子,输出为:

是了,for循环结构表示为:

for(表达式1;表达式2;表达式3)

{

do something;//执行动作;

}

那么,执行顺序为:period1:表达式1->表达式2->do someshing->表达式3

                               period2:->表达式2(若表达式2为真,往下进行,否则,至此终止)->do something->表达式3

                                  .........  ........

注意:1、上面的例子中,若表达式2写成:

i<10,printf("Second,i=%d\n",i)

 

那么,该循环就不会跳出,知道为什么吗?哈哈,逗号表达式!!!是的,i<10和printf("Second,i=%d\n",i),加上中间的逗号,构成了逗号表达式,函数printf(“Second,i+%d\n”,i)的返回值作为表达式2的值,(我在gcc编译器下printf返回值为3),也就是说,表达式2的值恒为真,无限循环。

2、

int i=0;
while(i++ <10)
{
    执行语句;
}


这里要十分注意,条件判断中i的取值为0~9,但执行语句中,i的取值为1~10!i与10比较后,先自加1,然后再执行语句!


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值