首页  /  世界杯2012  /  详细解读指针(一)——访问数据的地址

详细解读指针(一)——访问数据的地址

世界杯2012 2543

文章目录

编址指针变量解引用操作符*指针类型

指针的运算指针 +(-)整数指针-指针指针比大小

const修饰指针野指针野指针的可能成因规避野指针

assert断言传值调用和传址调用传值调用传址调用

数组和指针数组名的理解使用指针访问数组

在计算机中,每一个写入的数据都要在内存中存储,拥有一小块空间。每块空间都有一个地址,这个地址一个数字来表示。根据地址我们可以对其进行访问,同时地址也叫做指针。

为了方便数据存储,内存被划分成一个个小的内存单元,每个内存单元是一个字节(8个比特位)

1字节(byte)=8比特位(bit)1kb=1024byte1mb=1024kb1gb=1024mb1tb=1024gb

其中一个比特位可以存放一个二进制的1或者0

编址

在32为(x86)环境下,地址总线有32根(x64环境中64根),每根线都可以由0或1表示,所以地址就可以有2^32种表示方式。 也就是说,x86环境下,地址是由一个32位的二进制序列表示的,所以无论是什么类型数据的地址,它都占32个比特位的空间,也就是4个字节(x64环境中则是64个比特位,8个字节)。

通过地址总线,内存中的数据就可以被传递到CPU中的寄存器里 数据总线可以传输数据 控制总线控制数据是写入还是读取

指针变量

#include

int main()

{

int i = 10;

int* p = &i;

printf("%p\n", p);

return 0;

}

结果打印了一个一个8位16进制数字,也就是i的地址

int* 是变量p的类型,其中int表示其指向内容的类型是int,*表示它是指针变量p中存储了i的地址,即p指向i打印地址时占位符要用%p

解引用操作符*

#include

int main()

{

int i = 10;

int* p = &i;

*p = 20;

printf("%d\n", i);

return 0;

}

打印出来的结果是20

这说明p解引用后的*p可以用来访问该地址指向的内容

(注意:p是地址,*p是地址指向的内容)

指针类型

指针类型也分为char*,int*等等,虽然它们的长度都是4个字节,但是各自也有不同意义

#include

int main()

{

int i = 0x12345678;

int* p = &i;

char* pc = &i;

*pc = 0;

return 0;

}

当代码运行完第一句时,以及把0x12345678的值放到了i里面

但是当用char*类型的指针去访问i时,只能访问到地址最低的两位

也就是说,指针变量的类型决定了指针的“步长”,即指针能够访问到空间的大小

图中char类型的指针只能访问到一个字节的空间,而int类型可以访问4个字节

指针的运算

指针 +(-)整数

#include

int main()

{

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int* p = arr;//数组名是首个元素的地址

printf("%p\n", p);

p++;

printf("%p\n", p);

printf("%d\n", *p);

return 0;

}

可以看到,地址+1就相当于地址+“步长”,+n就是加上n倍的“步长”

这也就刚好保证了p++能访问到内存中下一个元素

(注意以上结论实现的前提是数组的元素随着下标是从低到高存储的)

指针-指针

#include

int main()

{

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int* p = arr;

printf("%p\n", p);

int*s = &arr[9];

printf("%p\n", s);

printf("%d\n", s - p);

return 0;

}

指针-指针的绝对值就是中间元素的个数 + 1(如果低地址-高地址就是负数)

注意前提是两个指针必须指向同一块区域

指针比大小

#include

int main()

{

int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

int* p = arr;

while(p <= &arr[9])

{

printf("%d ", *p);

p++;

}

return 0;

}

指针之间可以比较大小,用的时候要注意p++和p <= arr[9]是因为数组元素地址随着下标是递增的

const修饰指针

const修饰变量可以使该变量无法被修改,但是利用指针访问依然可以间接修改。

但是如果用const修饰指针,那么就有下面几种情况:const放在*前面或者后面,或者两边都放

const放在*前(与放在int的前后无关),那么该指针指向的内容(*p)不能改变,但是指针的指向(p)可以改变

#include

int main()

{

int i = 10;

int j = 20;

const int* p = &i;//const也可以放在int后面

*p = 0;//这条代码会报错

return 0;

}

const放在*后,那么该指针的指向(p)不能改变,但是可以通过该指针改变它指向的内容(*p)

#include

int main()

{

int i = 10;

int j = 20;

int* const p = &i;

p = &j;//这条代码会报错

return 0;

}

如果两边都放就都不能改变

野指针

野指针的可能成因

未初始化指针

int* p;

指针指向的内容销毁(空间释放)

int* test()

{

int n = 0;

return &n;

}//出函数时&n被保存在寄存器中传出来,但是n已经被销毁了!!!

int main()

{

int* p = test();

return 0;

}

指针访问越界

int arr[10] = 0;

int* p = arr[10];

规避野指针

指针初始化(如果提前不知道指针需要指向哪里,可以先置空int* p = NULL)及时置空(NULL)指针使用前检查有效性

assert断言

在debug版本中,判断括号内的内容,如果为真则不产生任何效果;如果为假程序报错 在release版本中,assert宏被优化掉了,无论是否为真都不产生任何效果

assert断言需要包含头文件

#include

//......

int* p = NULL;

assert(p != NULL)

此时程序会报错 如果需要禁用assert宏,需要在assert头文件前面加上#define NDEBUG这样assert语句就会失效

传值调用和传址调用

传值调用

传值调用是将实际参数的值传给形式参数,二者拥有独立空间,对形参的操作无法影响实参

#include

void Change(int a, int b)

{

int change = b;

b = a;

a = change;

}

int main()

{

int x = 3;

int y = 5;

Change(x, y);

printf("%d", x);

printf("%d", y);

return 0;

}

代码运行后,发现x和y实际上并没有交换,这就是因为改变形参不会改变实参

传址调用

#include

void Change(int* a, int* b)

{

int change = 0;

change = *b;

*b = *a;

*a = *change;

}

int main()

{

int x = 3;

int y = 5;

Change(&x, &y);

printf("%d", x);

printf("%d", y);

return 0;

}

把传入的变量变成地址后,形参和实参共用一个地址,所以是完全一样的,改变形参就是在改变实参

数组和指针

数组名的理解

在一般情况下,数组名就是首元素的地址,但有两种情况不同:

和数组在同一个函数内的sizeof(数组名),但是如果是数组传参后再用sizeof计算大小,此时的数组名就又算作首元素地址了,因为数组传参的本质是传首元素的地址,所以我们可以通过函数来改变数组

#include

void test(int* ptr)//这里用数组(int ptr[])或者指针接收都可以

{

printf("%d\n", sizeof(arr));

}

int main()

{

int arr[10] = {0};

test(arr);

return 0;

}

此时打印的结果就是指针长度4而不是数组长度40了

&数组名表示整个数组的地址,此时数组名就是整个数组,在数值上等于首元素地址,但是 &数组名+1跨越的长度是整个数组

使用指针访问数组

#include

int main()

{

int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

int sz = sizeof(arr) / sizeof(arr[0]);

int* p = arr;

int i = 0;

for(i = 0; i < sz; i++)

{

printf("%d ", *(p + i));

}

return 0;

}

这个代码可以完成对该数组的打印

此处的p换成arr,变成*(arr + i)也是可以的

那么就有*(arr + i)=*(i + arr)=arr[i]=i[arr]