不同的增值语句的区别

考虑下面四条语句:

1
2
3
4
x = x + 1; //正规形式
++x; //前缀自增
x++; //后缀自增
x += 1; //复合赋值

显然,这四条语句的功能是相等的,它们都把x的值增加1。
最后一条等价于第一条,没什么好说的。看看中间两条,前缀自增和后缀自增。

表达式++x先将x的值递增1,然后再使用变量x的值;
表达式x++则是先使用变量n的值,然后再将n的值递增1。

这些区别与编译器的中间代码有关,
++x表示取x的地址,增加它的值,然后把值放在寄存器中;
x++则表示取x的地址,把它的值装入寄存器中,然后增加内存中的x的值。

在一行代码中,仅有前缀自增或后缀自增的情况下,前缀方式和后缀方式的效果相同。
对于使用变量x的值的上下文来说,++x和x++的效果是不同的。如果x的值是5,那么:

1
n = x++;

执行后的结果是将n的值置为5(x先用后加)。

1
n = ++x;

执行后的结果是将n的值置为6(x先加后用)。

注意,自增与自减运算符只能作用于变量,类似于表达式(i+j)++是非法的。

宏定义

宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。例如,下列宏定义定义了一个宏max:

1
#define max(A,B) ((A) > (B) ? (A) : (B))

使用宏max看起来很像是函数调用,但宏调用直接将替换文本插入到代码中。形式参数(在此为A或B)的每次出现都将被替换成对应的实际参数。

因此,语句:

1
x = max(p+q, r+s);

将被替换为下列形式:

1
x = ((p+q) > (r+s) ? (p+q) : (r+s));

如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型,而无需针对不同的数据类型需要定义不同的max函数。

仔细考虑一下max的展开式,就会发现它存在一些缺陷。其中,作为参数的表达式要重复计算两次,如果表达式存在副作用(比如含有自增运算符或输入/输出),则会出现不正确的情况。例如:

1
max(i++;j++);

它将对每个参数执行两次自增操作。同时还必须注意,要适当使用圆括号以保证计算次序的正确性。考虑下列宏定义:

1
#define square(x) x * x

当用square(z+1)调用该宏定义时会出现错误结果。

可以通过#undef指令取消名字的宏定义,这样做可以保证后续的调用是函数调用,而不是宏调用:

1
2
#undef getchar
int getchar(void) {...}

形式参数不能用带引号的字符串替换。但是,如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数带引号的字符串中。例如,可以将它与字符串连接运算结合起来编写一个调试打印宏:

1
#define dprint(expr) printf(#expr " = %g\n",expr)

使用语句:

1
dprint(x/y)

调用该宏时,该宏将被扩展为:

1
printf("x/y" " = %g\n",x/y);

其中的字符串被连接起来了,这样,该宏调用的效果等价于:

1
printf("x/y = %g\n",x/y);

在实际参数中,每个双引号”将被替换为",反斜杠\将被替换为\\,因此替换后的字符串是合法的字符串常量。

预处理器运算符##为宏扩展提供了一种连接实际参数的手段。如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。例如,下面定义的宏paste用于连接两个参数:

1
#define paste(front,back) front ## back

因此,宏调用paste(name,1)的结果将建立记号name1。

大端存储与小端存储

所谓的大端模式(Big-endian),是指数据的高字节,保存在内存的低地址中,而数据的低字节,保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;

所谓小端模式(Little-endian), 是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内在的低地址中,这种存储模式将地址的高低和数据位 权有效结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致;

如果将一个32位的整数0x12345678存放到一个整型变量(int)中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。为简单起见,本文使用OP0表示一个32位数据的最高字节MSB(Most Significant Byte),使用OP3表示一个32位数据最低字节LSB(Least Significant Byte)。 

地址偏移 大端模式 小端模式
0x00 12(OP0) 78(OP3)
0X01 34(OP1) 56(OP2)
0X02 56(OP2) 34(OP1)
0X03 78(OP3) 12(OP0)

为什么有大小端之分:
因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。

判断一个机器是大端存储还是小端存储:
方法一:

1
2
3
4
int a = 0x12345678;
char c = ((char *)&a)[0];
if(c == 0x12) 大端
if(c == 0x78) 小端

方法二:

1
2
3
4
5
6
7
union {
int a;
char b[4];
}c;
c.a = 0x12345678;
if(c.b[0] == 0x12) 大端
if(c.b[0] == 0x78) 小端