//本篇列举了很多可能出错的地方,本篇重点在于避错
/*
本章分为四个部分:
1、指针声明和初始化
2、指针的使用问题
3、内存释放问题
4、使用静态分析工具
*/
#include<iostream>
#include <windows.h>
using namespace std;
/*
一、指针声明和初始化
(一)、不恰当的指针声明
*/
int *ptr1, ptr2;
/*
上述声明,会把ptr1声明为整数指针
ptr2声明为整数
*/
// 正确写法
int *ptrU1, *ptrU2;
// 更好的写法
int *ptrB1;
int *ptrB2;
/*
(二)、使用指针前未初始化
未初始化的指针,是野指针,内含垃圾变量
int *pi;
...
printf("%d\n",*pi); // 这样写不对
*/
/*
(三)、处理未初始化指针
1、用NULL
2、Assert
3、第三方工具
int *pi = NULL;
...
if(NULL == pi)
{
// 不应该解引pi
}
else
{
// 可以使用pi
}
assert(NULL != pi);// 表达式为真什么都不会发生,表达式为假,程序终止
*/
/*
二、指针的使用问题
缓冲区栈溢出,覆写对象边界以外的内存就会导致缓冲区溢出
可能触发缓冲区栈溢出的原因:
1、访问数组元素时候,没有检查索引值
2、对数组指针做指针运算符的时候不够小心
3、用gets这样的函数从标准输入读取字符串
4、误用strcpy和strcat这样的函数
*/
/*
(一)、测试NULL,用malloc这样的函数一定要检查返回值,否则会导致程序非正常终止
下面说明一般的方法
*/
void TestNULL()
{
float *vector = (float *)malloc(20 * sizeof(float));
if (NULL == vector)
{
// malloc分配内存失败
}
else
{
// 处理vector
}
}
/*
(二)、错误使用解引用操作
// 常用方法
int num;
int *pi = #
// 错误用法
int num2;
int *pi2;
*pi2 = &num2;
// 更正
int num3;
int *pi3;
pi3 = &num3;
*/
/*
(三)、米兔指针
释放指针后却仍然在引用原来的内存,就会产生迷途指针
*/
/*
(四)、越过数组边界访问指针(有可能程序崩溃)
char firstName = "1234567";
char middleName = "1234567"
char lastName = "1234567";
firstName[-2] = 'X';
middleName[0] = 'X';
lastName[8] = 'X';
*/
void Transboundary()
{
char firstName[8] = "1234567";
char middleName[8] = "1234567";
char lastName[8] = "1234567";
firstName[-2] = 'X';
middleName[0] = 'X';
lastName[8] = 'X';
printf("%s\n", firstName);
printf("%s\n", middleName);
printf("%s\n", lastName);
}
/*
(五)、错误计算数组长度
将数组传递给指针的时候,一定要同时传递数组长度,这样做能够帮助函数
避免数组越界
*/
void replace(char buffer[],char replacement,size_t size)
{
size_t count = 0;
while(*buffer != NULL&& count++ <size)
{
*buffer = replacement;
buffer++;
}
*buffer = '\0';
}
/*
name数组最多装7个字符,一个NULL
*/
void TestTranArraySize()
{
char name[8];
strcpy(name, "Alexand");
replace(name, '+', sizeof(name));
printf("%s\n", name);
}
/*
(六)、错误使用sizeof操作符
*/
/*
下面这段函数出错原因是,iSize = 20*4
正确计算方式是,iSize = siezeof(buffer)/sizeof(int)
*/
void errorSizeofUse()
{
int buffer[20];
int *pBuffer = buffer;
int iSize = sizeof(buffer);
for(int i = 0;i <iSize;i++)
{
*(pBuffer++) = 0;
}
}
/*
(七)、一定要匹配指针类型
short的十进制输出 %hd
short的十六进制输出 %hx 或者 %hX
*/
void matchPointerType()
{
int num = 2147483647;
int *pi = #
short *ps = (short *)pi;
printf("%p , %x, %d\n",pi,*pi,*pi);
printf("%p, %hx, %hd\n",ps,(unsigned short)*ps,(unsigned short)*ps);
}
/*
(八)、有界指针
有界指针是指指针的使用被限制在有效的区域内。比如说现在有一个32个元素的
数组,禁止对这数组使用的指针,访问数组前面或者后面的任何内存。
(方法有点麻烦,实际操作的时候,考虑静态分析)
*/
void PointerBoundary()
{
#define D_SIZE 32
char name[D_SIZE];
char *p = name;
if(name != NULL)
{
if(p >= name && p< name +D_SIZE)
{
// 有效指针 ,继续
}
else
{
// 无效指针,错误分支
}
}
}
/*
(九)、字符串安全问题
两种方法:
1、用户提供的格式化字符串传递给函数的时候,要谨慎
2、用strcpy_s这一类的函数,能够限制传递的字符串大小
(下面的函数是个反例)
*/
void StringSafe()
{
char firstName[8];
int iResult;
iResult = strcpy_s(firstName, sizeof(firstName), "Alexander");
}
/*
(十)、指针运算符和结构体
数组的内存连续,结构体的内存不连续,所以可以对数组使用算术运算,对结构体不能使用内存运算
误用对齐的指针可能会导致程序取到错误数据,或者程序异常终止。
如果编译器需要生成额外的机器码来弥补不恰当的对齐,那么指针访问也可能变慢
*/
typedef struct _employee
{
char name[10];
int age;
}Employee;
// 反面教材
void PointerStruct()
{
Employee employee;
// 不建议这样用
char * ptr = employee.name;
ptr += sizeof(employee.name);
}
typedef struct _item
{
int partnumber;
int quantity;
int bindNumber;
} Item;
void PointerStrTest()
{
Item part = {12345,35,107};
int *pi = &part.partnumber;
/*
指针偏移的方式访问
*/
cout << "PartNumber = "<<*pi << endl;
pi++;
cout << "Quantity = " << *pi << endl;
pi++;
cout << "Bindnumber = " << *pi << endl;
/*
直接取地址
*/
int *pi2 = &part.partnumber;
cout << "2 PartNumber = " << *pi2 << endl;
pi2 = &part.quantity;
cout << "2 Quantity = " << *pi2 << endl;
pi2 = &part.bindNumber;
cout << "2 Bindnumber = " << *pi2 << endl;
/*
根据对象直接取
*/
int iPartNumber = part.partnumber;
int iQuality = part.quantity;
int iBindnumber = part.bindNumber;
cout <<"3 iPartNumber" << iPartNumber << endl;
cout << "3 iQuality" << iQuality << endl;
cout << "3 iBindnumber" << iBindnumber << endl;
}
/*
(十一)、函数指针的问题
函数和函数指针用来控制程序的执行顺序,但是它们可能会被误用
导致不可预期的行为
*/
/*
三、内存释放问题
(一)、重复释放
(二)、清除敏感数据
char name[32];
memset(name,0,sizeof(name));
free(name);
name = NULL;
*/
void ClearInfo()
{
char name[32];
memset(name, 0, sizeof(name));
//free(name); // 注意,free是和malloc一起用的
char *pwd = (char*)malloc(sizeof(5));
//memset(pwd, 0, sizeof(pwd)); // 小心溢出
free(pwd);
pwd = NULL;
//name = NULL;
}
/*
四、使用静态分析工具
gcc编译器加上-Wall启用 编译器警告
*/
int main()
{
//TestTranArraySize();
//errorSizeofUse();
//matchPointerType();
//StringSafe();
//PointerStrTest();
//Transboundary();
ClearInfo();
system("pause");
return 0;
}