#指针
32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做一个地址,那么一个地址就是32个bit位,需要4个字节才能存储。 如果指针变量是用来存放地址的,那么指针变量的大小就得是4个字节的空间才可以。 同理64位机器,假设有64根地址线,一个地址就是64个二进制位组成的二进制序列,存储起来就需要8个字节的空间,指针变的大小就是8个字节
结论:
•32位平台下地址是32个bit位,指针变量大小是4个字节• 64位平台下地址是64个bit位,指针变量大小是8个字节• 注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的
指针变量类型的意义
指针变量的大小和类型无关,只要是指针变量,在同一个平台下,大小都是一样的,为什么还要有各种各样的指针类型呢?
指针的解引用
#include /* 代码1 */
int main()
{
int n = 0x11223344;
int *pi = &n;
*pi = 0;
return 0;
}
#include /* 代码2 */
int main()
{
int n = 0x11223344;
char *pc = (char *)&n;
*pc = 0;
return 0;
}
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第一个字节改为0。 结论:指针的类型决定了,对指针解引用的时候有多大的权限(一次能操作几个字节)。 比如: char * 的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字节
#include
int main()
{
int n = 10;
char *pc = (char*)&n;
int *pi = &n;
printf("%p\n", &n); /* 输出的是变量n的地址,表示的是32位地址 */
printf("%p\n", pc); /* pc与`&n`的地址相同(强制类型转换不改变地址值),仍输出`0x7ffd1234` */
printf("%p\n", pc+1); /* */
printf("%p\n", pi); /* */
printf("%p\n", pi+1); /* */
return 0;
}
**_*char * 类型的指针变量+1跳过1个字节, int_ 类型的指针变量+1跳过了4个字节。这就是指针变量的类型差异带来的变化。 结论:指针的类型决定了指针向前或者向后走一步有多大***
“ *pc ”说明表示的是一个指针变量,并且地址存储在pc。
char pc = (char)&n;
&n
**:取变量n
的地址,此时类型为int*
(指向整型的指针)。(char*)
**:强制类型转换操作符,将int*
类型的指针转换为char*
类型的指针。char *pc
**:定义一个字符型指针变量pc
,用于存储转换后的地址。但是可能会引起数据截断等,
强制类型转换不改变指针存储的地址值,仅改变对内存数据的解释方式
指针变量存储的地址值大小由系统架构决定(如32位系统为4字节,64位系统为8字节),与指针类型无关
C语言编译器是顺序执行,第一次看到p认为是指针变量,
后面已经认为是指针了。
然后就可以通过p访问里面的数值。
使用取地址,如果是单一一个变量那么只能看到的是基地址。
一定是地址最小值赋值给p1。
指针指向内存空间,一定要保证合法性,也就是能读能写。
思考题:
大小端地址,也就是堆和栈,本文在这里不进行讨论。
指针地址的实际理解:
float a = 1.2; // 4个字节
C语言的数组也是指针。可以认为没有数组。
#include
int main() {
#if 0
int a = 10;
int *pa = &a; // 存储a的地址到pa
char *p2;
printf("a的地址:%p\n", (void*)&a); // 输出如0x004ffed0
printf("pa的值:%p\n", pa); // 输出与上一行相同
printf("p2的值:%p\n", p2);
*pa = 20; // 通过指针修改a的值
printf("a的新值:%d\n", a); // 输出20
#endif
int a = 6222662;
int *p;
p = &a;
printf("地址值:%x\n", p);
unsigned char *byte_ptr = (unsigned char*)p;
for (int i = 0; i < sizeof(int); i++)
{
printf("字节%d地址:%p | 值:0x%x\n",
i, (void*)(byte_ptr + i), byte_ptr[i]);
}
char *cp = (char *)&a;
printf("cp地址值:%x\n", cp);
}
输出:
地址值:2d9ffa3c
字节0地址:000000402d9ffa3c | 值:0x46
字节1地址:000000402d9ffa3d | 值:0xf3
字节2地址:000000402d9ffa3e | 值:0x5e
字节3地址:000000402d9ffa3f | 值:0x0
cp地址值:2d9ffa3c
首先我们可以看出我们输出的地址只能是首地址,
此外我们首先明白十进制数6222662转换为16进制是
0x5ef346
这个时候我们需要记住一句话,不管我们的地址是多大,它最多可以存一个字节的数据,也就是5位。
那么我们可以看出 基于从小到大的理解,我们在这里不深入进行讨论大小端问题,因为目前水平还没有达到,在后续文章会进一步探讨。
单纯的从内容分析:
我们先将0x5ef346从低字节开始划分
0x46、0xf3、0x5e、0x00
也就是需要依次存储在内存空间中(值得就是内存地址)
那么根据输出可以看出,数据0x46存储的地址是000000402d9ffa3c
以此类推下一个字节就需要将地址加1,也就是000000402d9ffa3d存储的是f3,接着就是剩下的。
思考:
也可以使用其他思路去验证。
备注:。这一机制提供了底层操作的灵活性,但需谨慎处理数据完整性、对齐和类型兼容性问题。
变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量。 但是如果我们希望一个变量加上一些限制,不能被修改,怎么做呢?这就是const的作用
#include
//代码1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;
p = &m;
}
备注:
*p = 20; 可以理解为 *p本身表示的是一个地址,相当于是我直接认为的找到了一个地址,直接把这个地址之前的值给覆盖掉,直接存储20。
const为常量,只读性质。[ 暂时认为不能改变 ]
const 看到的第一瞬间就是常量。
在指针应用中,然后在分析,怎么个不可变性。
常量指针和指针常量
变量指针和指针变量
==这两个暂时认为是一样的==
const char * p 以这种写法为主要。
**char const * p**
首先确定的是p是一个变量
那么这p可以指向任何一个空间,但是指向的内容是不会被改变的。
也就是这个地址所存储的值是不会被改变的。这个地方相当是指向的是字符串常量( 字符串不变性。当然不一定是字符串,而是为了说明我指向的内容是不可以改变的。只是用字符串去举例子。)
这个地方就是指向的地址可以改变,但是地址的内容是不能改变的,
换句话说,一个学生(指针变量p)可以学习很多课目,例如数学、语文、英语(多个地址)等,但是在学习的过程中只能学习到对应的知识,是不能被改变的(不能说我在学习语文的时候还能学习数学,学习的唯一性。)。
const char * p 还可以认为 const修饰(char * p)
==这两个暂时认为是一样的==
char * const p; 以这种写法为主要。
**char * p const;**
那么这*p只能指向一个内存,一般都是硬件资源的配置。
但是指向的内容是可以改变的,就是我可以存储不同的东西。但是我这个p一定是只能指向这个地址。 这个地方怎么理解呐,可以理解为是一个LCD显示缓存,我们通过填充不同的内容,然后读取进而显示。因为我们的LCD一定需要显示内容,因此我们可以直接固定一个地址,就让这个地址去做缓存显示用,那么我在用这个变量去指向这个地方,就实现了显示不同内容的效果。
多用来修饰硬件资源。
地址是死的,在焊接芯片的时候直接给他搞死。
const char * const p 一般是ROM空间。
地址不能变,并且地址里面的内容也是不能变的,只读存储器。
#include
//代码1
***
无修饰内容,很好理解。
***
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;
p = &m;
}
***
**• const如果放在 * 的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指针指向的内容,可以通过指针改变**。
***
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;
p = &m; x
}
void test4()
{
int n = 10;
int m = 20;
int const * const p = &n;
*p = 20; x
p = &m; x
}
int main()
{
//测试⽆const修饰的情况
test1();
//测试const放在*的左边情况
test2();
//测试const放在*的右边情况
test3();
//测试*的左右两边都有const
test4();
return 0;
}
***
const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变**
const int* p = &n;
`const` 修饰的是 `*p`(指针指向的数据),而非指针变量 `p` 本身
这意味着 **`p` 指向的整型数据 `n` 被视为常量**,不能通过 `p` 修改 `n` 的值,但 `p` 的指向可以自由改变。
***
void test2()
{
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20; x
p = &m;
}
可以看出,我们在使用const修饰指针指向的内容(或者说是地址储存的内容)的时候,我们不能修改这个内容,也就是不能通过给这个地址重新装入一个值,来改变原有的值,但是我们却可以改变这个地址,从而改变这个指针变量代表的值,其实这个过程就是相当于表达重新给变量p找到了一个新的内存,从而进行了底层的改变。为了改变这个值属于是投机取巧的思路,有点牛的 。
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20;
p = &m; x
}
• const如果放在 * 的右边,修饰的是指针变量本身,保证了指针变量的地址不能修改,但是指针指向的内容,可以通过指针改变。
也就是说内存不会改变。相当于是作为显示的缓存地址使用,同一个地址,用来显示不同的内容。
如何记忆呐,
靠近谁,谁不会被改变,但是可以从另外一个角度改变。
指针的基本运算有三种,分别是: • 指针±整数 • 指针-指针 • 指针的关系运算
因为数组在内存中是连续存放的,比如int类型的数组,每个元素相差4个字节,因此我们只需要知道首元素的地址就可以通过加减的方式找到后面元素的地址。
int arr[10] = {1,2,3,4,5,6,7,8,9,10}
#include
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));
p+i 这⾥就是指针+整数 i每增加1就往后移动4个字节
}
return 0;
}
指针增加,那就是地址会直接加四,并不是只是加“1”,是字节的增加。加一次是增加四个字节的大小。
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的
指针未初始化
如果明确知道指针指向哪里就直接赋值地址,如果不知道指针应该指向哪里,可以给指针赋值NULL(空指针,也可以理解为0,但是不完全是0,因为0有整形和char类型,只是有那个意思) NULL 是C语言中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问
所以在使用指针的过程中:
int *p = &a
相当于是
【版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。】