抄录
请记住 Peter Norvig 说的,学会编程通常需要十年,为何人人都这么着急?
无符号、有符号运算
当有符号和无符号数在同一个运算里的话,是将有符号提升为无符号数运算。
int x;
unsigned int y,z;
x = -1;
y = 2;
z = x * y;
z 的二进制结果为:0xfffffffe。
int x;
unsigned int y,z,k,m;
x = -2;
y = 1;
z = 500 + (x + y) * 100;
k = (x + y);
m = k*100+500;
z = 400; k = 4294967295; m = 400;
虽然结果正确,但是并不表示过程是正确的。
数组和指针
例1:
int a[10];
int *aa;
for (i = 0; i< 10; i++)
{
a[i] = i+1;
}
aa = (int **) &a;
**aa = 1;
编译没有报错,运行时提示为无效指针写错误。
例2
int a[10];
int **aa;
aa = (int **) &a;
*aa = a;
*(*aa + 0) = 0;
编译无错,运行无错,a[0] = 0;
例3
int a[10];
int **aa;
aa = (int **) &a;
*aa = a;
*(*aa + 0) = 0;
*(*aa + 1) = 1;
编译没有报错,运行时提示为无效指针写错误。
例4
int i ;
int a[10];
for (i = 0; i< 10; i++)
{
a[i] = i;
}
int **aa;
aa = (int **) &a;
*aa = a;
编译、运行没有错误,但是a[0]中的数值改变且变为自身a的地址。
观察调试环境
可以发现一维数组a的地址跟其指向的地址相同。如果硬要把a理解为指针,那么a就是指向自己本身的指针。
然而定义一个指针变量int *p
,包含了两层意思,首先p是一个变量,变量就有自己存储空间,其地址为&p。其次p存储空间中可以存储数据。以下图为例. *p = 1
是将数值1存放到地址为0xffff1111的内存中。
显然把一维数组a理解为指向自身的指针是有问题的,将下图理解为一维数组的话,a[0] = 1
的结果是地址0xffff0000中值变为1,一维指针里存放的数据应该是一个地址,而非要赋值的数据。
综上,将一维数组中int a[10]
的a理解为地址数值更合适。所以&a
理解对数值取地址还是数值本身。
分析
例1的图示如下。aa取得的数据为0xffff1111, *aa取得的数据为1,**aa=1
就是将数值1赋值给地址1,显然0x0000001为无效地址。
同理可以分析例2和例3 , 其中*(*aa+1)= 1
是将数值1赋值给地址为0x00000005的内存,显然也是无效内存。
综上,可以说明一维数组变量仅仅只能代表一个地址数值,跟一维指针还是有所不同的。那么二维数组呢?
还是先看看一维数组的汇编代码。
int *p;
int a[10];
a[0]= 1;
a[1]= 2;
a[2]= 3;
p = a;
*p = 1;
*(p+1)= 2;
//汇编代码
int *p;
int a[10];
a[0]= 1;
00EF352E mov dword ptr [a],1
a[1]= 2;
00EF3535 mov dword ptr [ebp-34h],2
a[2]= 3;
00EF353C mov dword ptr [ebp-30h],3
p = a;
00EF3543 lea eax,[a]
00EF3546 mov dword ptr [p],eax
*p = 1;
00EF3549 mov eax,dword ptr [p]
00EF354C mov dword ptr [eax],1
*(p+1)= 2;
00EF3552 mov eax,dword ptr [p]
00EF3555 mov dword ptr [eax+4],2
观察a[1]= 2;
和*(p+1)= 2;
处的汇编代码,可以看出对数组的取地址行为是编译器计算出相对于函数栈的偏移地址,而对于指针则是编译器计算出相对于指针地址的偏移地址。进一步说明二者的不同。
观察二维数组可以得出同样结论。
int b[2][2];
b[0][0] = 1;
0116352E mov dword ptr [b],1
b[0][1] = 2;
01163535 mov dword ptr [ebp-10h],2
b[1][1] = 4;
0116353C mov dword ptr [ebp-8],4
如下贴出二维数组和二维指针的一个错误例子,错误的关键就是数组的取地址方式跟指针的取地址方式不一样。
int **p;
int b[2][2];
b[0][0] = 1;
003A352E mov dword ptr [b],1
b[0][1] = 2;
003A3535 mov dword ptr [ebp-1Ch],2
b[1][1] = 4;
003A353C mov dword ptr [ebp-14h],4
p = (int **)b;
003A3543 lea eax,[b]
003A3546 mov dword ptr [p],eax
p[1][1] = 1;
003A3549 mov eax,dword ptr [p]
003A354C mov ecx,dword ptr [eax+4]
003A354F mov dword ptr [ecx+4],1
然而一维数组变量是可以直接赋值给一维指针的,但是这只是运算结果相等的巧合,所以还是那句话,结果正确并不代表过程也正确。
最后总结一点,数组更好的迎合了栈的使用,而指针更好的迎合了堆的使用,C语言之所以经得起时间的考验就不太可能存在“既生瑜何生亮”的两种东西。