文章目录
- 1. const修饰指针
-
- 1.1 const修饰变量
- 1.2 const修饰指针变量
- 2. 野指针
-
- 2.1 野指针成因
-
- 2.1.1 指针未初始化
- 2.1.2 指针越界访问
- 2.1.3 指针指向的空间释放
- 2.2 如何规避野指针
-
- 2.2.1 指针初始化
- 2.2.2 小心指针越界
- 2.2.3 指针不用时置NULL,使用前检查有效性
- 2.2.4 避免返回局部变量的地址
- 3. assert断言
-
- 3.1 assert的基本使用
- 3.2 assert的开关机制
- 3.3 assert的优缺点
- 4. 指针的使用和传址调用
-
- 4.1 strlen的模拟实现
- 4.2 传值调用和传址调用
-
- 4.2.1 传值调用
- 4.2.2 传址调用
- 4.2.3 传值调用与传址调用的使用场景
1. const修饰指针
1.1 const修饰变量
普通变量的值可直接修改,也可通过指向它的指针间接修改;被const修饰的变量,在语法层面被限制直接修改,成为只读变量,但仍可通过指针绕开const限制间接修改。 示例代码:
#include <stdio.h>
int main()
{
const int n = 0;
printf("n = %d\\n", n);
int* p = &n;
*p = 20;//绕开const修改n的值
printf("n = %d\\n", n);
return 0;
}
为避免指针打破const对变量的修饰限制,需将const修饰指针变量,限制指针的操作权限。
1.2 const修饰指针变量
const修饰指针变量时,根据放在*左侧或右侧,有不同的修饰效果,也可同时修饰*的左右两侧,实现双重限制。通过以下代码分析不同修饰方式的效果:
#include <stdio.h>
//代码1 – 无const修饰
void test1()
{
int n = 10;
int m = 20;
int* p = &n;
*p = 20;//ok
p = &m; //ok
}
//代码2 – const放在*的左边
void test2()
{
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//error
p = &m; //ok
}
//代码3 – const放在*的右边
void test3()
{
int n = 10;
int m = 20;
int * const p = &n;
*p = 20; //ok
p = &m; //error
}
//代码4 – *的左右两边都有const
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; //error
p = &m; //error
}
int main()
{
test1();
test2();
test3();
test4();
return 0;
}
核心结论:
2. 野指针
概念:野指针是指指针指向的位置不可知(随机的、不正确的、没有明确限制的),使用野指针会导致程序运行异常,是C语言中常见的错误。
2.1 野指针成因
2.1.1 指针未初始化
局部指针变量若未显式初始化,其值为随机的内存地址,此时解引用操作该指针,会访问非法内存空间,形成野指针。
#include <stdio.h>
int main()
{
int *p;//局部指针未初始化,值为随机值
*p = 20;//非法访问内存,p为野指针
return 0;
}
2.1.2 指针越界访问
指针指向数组等连续内存空间时,若访问的范围超出该空间的合法边界,会形成野指针。
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i = 0; i <= 11; i++)
{
*(p++) = i;//i=10、11时,指针超出数组范围,成为野指针
}
return 0;
}
2.1.3 指针指向的空间释放
函数中的局部变量存放在栈区,函数执行结束后,局部变量的内存空间会被系统释放,若函数返回该局部变量的地址,接收该地址的指针会指向已释放的无效空间,形成野指针。
#include <stdio.h>
int* test()
{
int n = 100;//局部变量,函数结束后空间释放
return &n;
}
int main()
{
int*p = test();//p指向已释放的空间,成为野指针
printf("%d\\n", *p);
return 0;
}
2.2 如何规避野指针
2.2.1 指针初始化
指针定义时必须显式初始化:
NULL是C语言定义的标识符常量,本质是地址((void *)0),该地址无法读写,是指针的合法初始值。
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = #//明确指向,直接赋值地址
int*p2 = NULL;//暂时无指向,赋值NULL
return 0;
}
2.2.2 小心指针越界
指针只能访问程序向内存申请的合法空间,严格控制指针的访问范围,避免超出空间边界,尤其在遍历数组时,注意循环结束条件。
2.2.3 指针不用时置NULL,使用前检查有效性
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
for(i = 0; i < 10; i++)
{
*(p++) = i;
}
p = NULL;//指针越界后,及时置NULL
p = &arr[0];//重新获取地址
if(p != NULL) //使用前检查有效性
{
//执行指针操作
}
return 0;
}
2.2.4 避免返回局部变量的地址
栈区的局部变量随函数执行结束而释放,其地址会变为无效地址,因此函数不要返回局部变量的地址,若需返回值,可直接返回变量本身,或使用全局变量、动态内存分配。
3. assert断言
assert是C语言的断言宏,定义在<assert.h>头文件中,用于运行时检查程序的指定条件是否成立,是排查程序错误、规避野指针的重要工具。
3.1 assert的基本使用
assert接收一个表达式作为参数:
核心用法:使用指针前,断言指针非NULL,避免解引用野指针。
assert(p != NULL);//检查指针p是否为NULL,非NULL才继续执行
3.2 assert的开关机制
assert的检查可通过宏NDEBUG灵活开启或关闭,无需修改代码:
#define NDEBUG //定义后,禁用assert
#include <assert.h>
3.3 assert的优缺点
优点
缺点:引入额外的条件检查,会轻微增加程序的运行时间,因此通常在Debug版本中使用,Release版本中禁用。主流集成开发环境(如VS)会在Release版本中自动优化掉assert语句。
4. 指针的使用和传址调用
4.1 strlen的模拟实现
库函数strlen的功能是统计字符串中\\0之前的字符个数,函数原型为size_t strlen ( const char * str ),参数str用const修饰,保证指针不会修改字符串内容。
模拟实现思路:
参考代码:
#include <stdio.h>
#include <assert.h>
int my_strlen(const char * str)
{
int count = 0;
assert(str);//断言指针非NULL
while(*str)//判断当前字符是否为'\\0'
{
count++;
str++;
}
return count;
}
int main()
{
int len = my_strlen("abcdef");
printf("%d\\n", len);//输出:6
return 0;
}
4.2 传值调用和传址调用
4.2.1 传值调用
概念:调用函数时,将实参的值传递给形参,编译器会为形参单独创建临时空间接收实参值,形参和实参是两个独立的内存空间。 特点:对形参的修改不会影响实参的值,因此传值调用无法实现修改主调函数中变量的需求。
反例:传值调用交换两个整型变量(失败)
#include <stdio.h>
void Swap1(int x, int y)//x、y为形参,接收实参值
{
int tmp = x;
x = y;
y = tmp;//仅交换形参x、y的值,与实参无关
}
int main()
{
int a = 10, b = 20;
printf("交换前:a=%d b=%d\\n", a, b);
Swap1(a, b);//传值调用
printf("交换后:a=%d b=%d\\n", a, b);//a、b值未改变
return 0;
}
4.2.2 传址调用
概念:调用函数时,将实参的地址传递给形参,形参为指针变量,接收实参的地址,通过指针可间接访问并修改实参的内存空间。 特点:让被调函数和主调函数建立真正的内存联系,被调函数可通过指针修改主调函数中的变量值,是指针的核心应用场景。
正例:传址调用交换两个整型变量(成功)
#include <stdio.h>
void Swap2(int*px, int*py)//px、py接收a、b的地址
{
int tmp = 0;
tmp = *px;//通过指针访问a的值
*px = *py;//将b的值赋给a
*py = tmp;//将原a的值赋给b
}
int main()
{
int a = 10, b = 20;
printf("交换前:a=%d b=%d\\n", a, b);
Swap2(&a, &b);//传址调用,传递a、b的地址
printf("交换后:a=%d b=%d\\n", a, b);//a、b值成功交换
return 0;
}



