指针(2)
- 前言
- const 关键字
-
- 修饰指针变量
-
- *号前
- *号后
- 野指针
-
- 野指针的定义
- 成因
-
- 未初始化
- 越界访问
- 访问已释放的空间
- 预防
-
- NULL
- 不要越界访问
- 注意空间释放
- 指针调用
-
- 传值调用
-
- 代码
- 传址调用
-
- 代码
前言
本文接着指针(1)继续分享C语言指针的相关知识,上文链接: 指针(1)。
const 关键字
const 中文注释: 常量的;
在C语言中,const 关键字用于声明一个变量为常量,这意味着一旦变量被赋予了值,它就不能被修改。
修饰指针变量
而当const修饰指针变量时,同样具备值不能修改的特性,但是在指针变量中又因为位置的不同而具有不同的效果。
*号前
const int * a;
int const * a;
上述的两种写法的效果是一致的且都保持了const在*号前的特点。
当const 在* 前修饰变量时,*p的值是不可修改的。
我们可以理解成这个指针变量指向的空间内的值是不可修改的。 不使用const修饰的情况: 在*号前修饰的情况下是无法对 *a1进行修改,即对a1所指向的地址内存储的值不可修改。
但是请注意,a1所指向的地址内存储的值不能修改,并不代表a1所指向的地址不可修改!!!
*号后
而这种情况,则是将指针变量的所指向的地址修饰为不可修改,即地址不可修改,但是地址内容可以修改。
int a = 10;
int b = 5;
//使用const修饰 在*号后
int* const a1 = &a;
*a1 = 66;
printf("%d", *a1);
结果为66 ,当我们尝试修改a1地址时候会发现…
int a = 10;
int b = 5;
//使用const修饰 在*号后
int* const a1 = &a;
//修改a1所指向的地址
a1 = &b;//报错
printf("%d", *a1);
总结: 当const在 * 号前修饰指针变量时,指针指向地址内存储的数据不可修改,但是地址可修改; 当const在 * 号后修饰指针变量时,指针所指向的地址不可修改,但地址内存储的数据可修改; 当前后都有const,地址和内容都不可修改。
野指针
野指针的定义
野指针(Dangling Pointer)是指向无效内存或者已被释放的内存的指针。指向一个 “随机” 的内存位置,这个位置可能包含任何数据,甚至可能是另一个正在运行的程序的数据。使用野指针可能导致程序崩溃、数据损坏或安全漏洞。
成因
主要有以下三种成因:未初始化 、 越界访问、访问已释放的空间。
未初始化
int* a;
当我们定义了一个指针变量但是没有初始化,编译器会随机给它一个地址,这个地址谁也不知道会访问到哪里。
越界访问
int arr[5]={1,2,3,4,5};
int * p = arr[10];
可以看到数组只有5个元素,当使用指针变量去访问到第10个元素的时候就会造成越界访问,可能会进入到其他程序的空间导致程序崩溃。 但是因为位置的可控,也使得我们能通过指针的手段进入到其他程序的内存中进行访问、修改、删除等操作。
访问已释放的空间
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 2;
printf("%d",Add(a,b));
int* p = &z;
return 0;
}
在函数的章节,我们知道了形参和实参的概念,函数内的变量的生命周期就是函数调用开始和结束,当函数结束后原来的空间就会被释放。 当我们去指针去访问的已经被释放的空间就会形成野指针。
预防
那么我们该怎么在开发过程中避免野指针呢?
NULL
对于未初始化的情况,我们在定义一个指针变量的时候就要初始化,如果暂时不知道指向哪个地址的时候,用NULL代替。
int* p = NULL;
在使用变量前,将地址补充。
不要越界访问
在使用指针变量的时候注意不要越界访问。
注意空间释放
注意空间的释放,可以使用static关键字避免空间被释放。
static int z;
int Add(int x, int y)
{
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 2;
printf("%d",Add(a,b));
int* p = &z; //不会报错
return 0;
}
指针调用
在编程语言中,特别是在C和C++中,函数参数的传递方式主要有两种:传值调用(Call by Value)和引用调用(Call by Reference)。这两种传递方式在函数执行时对实参(actual argument)和形参(formal parameter)的影响上有本质的区别。
传值调用
传值调用是最常见的参数传递方式。在这种方式中,实参的值被复制给形参。在函数体内,形参是实参的一个副本,因此对形参的任何操作都不会影响到实参。这意味着,如果函数内部改变了形参的值,实参的值是不会变的。例如:
代码
int Add(int x,int y)
{
return x+y;
}
int main()
{
int a = 1;
int b = 2;
int c = Add(a,b);
return 0;
}
传址调用
传址调用是另一种参数传递方式,它允许被调用函数能够修改调用函数中的变量。在这种方式中,实参的地址被传递给形参,这样形参就成为了实参的一个别名。因此,对形参的任何操作都会直接反映在实参上。传址调用在C中通过指针实现。例如:
代码
int Swap(int*x, int* y)
{
int temp = *x;
*x = *y;
*y = temp;
return z;
}
int main()
{
int a = 10;
int b = 2;
//交换a和b的数值
Swap(&a, &b);
printf("a = %d\\nb = %d", a, b);
return 0;
}
评论前必须登录!
注册