作为函数调用使用,指针和数组是相同的,在传递参数时,数组实际上被传递了指针
1. 表达式中的数组名(与声明不同)被编译器当作一个指向该数组第一个元素的指针
2. 下标总是与指针的偏移量相同
3. 在函数参数的声明中,数组名被编译器当作指向该数组第一个元素的指针
对一个数组的访问总是可以写成一个指针加一个偏移量的形式
对数组下标的引用总是可以写成“一个指向数组的起始位置的指针加上偏移量”
int a[10], *p, I = 2;
对数组的访问总是可以写成一个指向数组起始地址的指针加上偏移量:
p = a; p[i] p= a; *(p + i); p = a + I; *p
a[6] 等价于 6[a]…
在传递参数时,数组被转化为指针,指向数组的第一个数的位置
为了效率
&就是为了传地址
数组和函数是传址,其他都是传值
使用assert断言
aasert是个只有定义了DEBUG才起作用的宏,如果其参数的计算结果为假,就中止调用程序的执行。因此在上面的程序中任何一个指针为NULL都会引发assert
#ifdef DEBUG
void _Assert(char* , unsigned); /* 原型 */
#define ASSERT(f) \
if(f) \
NULL;\
else \
_Assert(__FILE__, __LINE__)
#else
#define ASSERT(f) NULL
#endif
void _Assert(char* strFile, unsigned uLine)
{
fflush(stdout);
fprintf(stderr, “\nAssertion failed: %s,line %u\n”,strFile, uLine);
fflush(stderr);
abort();
}
经常停下来看看程序中有没有使用无定义的特性
如果在存储空间相互重叠的对象之间进行了拷贝,其结果无定义
要使用断言对函数参数进行确认
void memcpy(void*pvTo, void* pvFrom, size_t size)
{
void* pbTo = (byte*)pvTo;
void* pbFrom = (byte*)pvFrom; 25
ASSERT(pvTo != NULL && pvFrom !=NULL);
ASSERT(pbTo>=pbFrom+size ||pbFrom>=pbTo+size);
while(size-->0)
*pbTo++ == *pbFrom++;
return(pvTo);
}
当程序员刚开始使用断言时,有时会错误地利用断言去检查真正地错误,而不去检查非法的况
assert的用意是用来检测非法的情况!!!
消除所做的隐式假定,或者利用断言检查其正确性
命名的问题!!!清晰明了而富有含义
在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了相应的假定,就要使用断言对所做的假定进行检验,或者重新编写代码去掉相应的假定。另外,还要问:“这个程序中最可能出错的是什么,怎样才能自动地查出相应的错误?”努力编写出能够尽早查出错误的测试程序。
捕捉错误的最好办法是在编写或修改程序时进行相应的检查。那么,程序员测试其程序的最好办法是什么呢?是对其进行逐条的跟踪,对中间的结果进行认真的查看.
对每一条代码路径进行逐条的跟踪
对代码进行逐条跟踪的真正作用是它可以使我们观察到数据在函数中的流动。如果在对代码进行逐条跟踪时密切地注视数据流,就会帮助你查出下面这么多的错误:
上溢和下溢错误;
l 数据转换错误;
l 差1错误;
l NULL指针错误;
l 使用废料内存单元错误(0xA3类错误);
l 用 = 代替 == 的赋值错误;
l 运算优先级错误;
l 逻辑错误。
源级调试程序可能会隐瞒执行的细节对关键部分的代码要进行汇编指令级的逐条跟踪
char c;
c = getchar();
if( c == EOF )
......
在不进行符号扩展的机器上,c总是正数因为它是char类型而EOF却是负数,结果上面的测试条件总会失败。为了避免这一点,必须用int而不用char来保存getchar返回值的变量。