关于C语言指针那些事儿
- 指针是什么?你可以把它理解为一个变量类型,对!没错,一个变量类型,无论是int也好char也罢,都只是一个指针类型,没有差别,但是也有差别,也许你们常常会像下面这样使用指针
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 123;
int* pointer = &a;
printf("a:%d\npointer:%p\n*pointer:%d\n",
a,
pointer,
*pointer);
return 0;
}
- 打印出来的结果显而易见,a的值是123,pointer的值是个地址0x7ffff91d4d2c,*pointer是0x7ffff91d4d2c里面储存的值123
a:123
pointer:0x7ffff91d4d2c
*pointer:123
- 但是这不是我们要讨论的指针,真正的指针是要抛开类型的限制,我们看下面一段代码
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 123;
char* pointer = (char*)&a;
printf("a:%d\npointer:%p\n*pointer:%d\n",
a,
pointer,
*pointer);
return 0;
}
- 这段代码有何不同呢?没错!pointer的类型变成了char*,我们来看看结果,发现并没有什么变化,pointer的值发生变化是因为进程运行时分配给进程的内存都是随机的
a:123
pointer:0x7ffff842f12c
*pointer:123
- 这说明只要是指针,不管是什么数据类型,它都可以指,我们再来以%c输出*pointer看看
a:123
pointer:0x7fffcd3a390c
*pointer:{
- 很显然
{
对应的ASCII码值就是123,你以为这就结束了?真的只要带上星号的类型都是一样的吗?我们都知道char是一个字节,int是四个字节,所以char指针加减1是偏移1个字节,int指针每次加减1就是偏移4个字节,这也就是不同类型指针间最大的区别,既然知道了这点,我们来看看下面的代码。
#include <stdio.h>
int main(int argc, char const *argv[])
{
char s[8] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
int* pointer = (int*)s;
while (*pointer)
{
printf("%c\n", *pointer);
pointer++;
}
return 0;
}
- 根据上面的理论,这段代码的输出应该是a和e,结果也确实不出所料
a
e
- 看到现在你是不是对指针有了一点新的感觉了呢?别急,还没结束呢?我们都知道C语言中函数名就是个地址,和数组名一样,那么函数名不也可以赋值给一个指针吗?这是当然的,因为有函数指针嘛,我们现在要讨论的是普通数据类型的指针可以吗?再来看下面的代码。
void function(){}
#include <stdio.h>
int main(int argc, char const *argv[])
{
void (*fp)() = function;
int *p = (int *)function;
printf("fp:%p\np:%p\n",fp,p);
return 0;
}
- 这里定义了一个函数指针fp和一个普通指针p,都指向函数function,我们先来看看结果
fp:0x7f3722b95149
p:0x7f3722b95149
- 值果然是一样的,这至少能说明int指针也可以指向一个函数所在的地址,现在我们来玩一下骚操作。
#include <stdio.h>
void function()
{
printf("Hello Pointer\n");
}
int main(int argc, char const *argv[])
{
function();
int *p = (int *)function;
*p = 0;
function();
return 0 ;
}
- 这里我们在function函数里面写了一个输出语句,然后在main中调用它,然后我们让p指针指向function,然后修改p指针指向的内存的值为0,并再次调用function,果然不出所料
Hello Pointer
[1] 1127 segmentation fault (core dumped) ./demo
- 程序直接发生了异常,我们来用gdb分析一下,这个过程
gef➤ p function
$4 = {void ()} 0x8001149 <function>
gef➤ x 0x8001149
0x8001149 <function>: 0xfa1e0ff3
- 可以看到函数名所指的内存是0x8001149,我们可以我们在改变p的值之前内存中的值是0xfa1e0ff3,我们继续运行,直到p被重新赋值
gef➤ x 0x8001149
0x8001149 <function>: 0x00000000
- 我们可以看到这个时候0x8001149的值已经变成了0,到这里你是不是对指针有有了不一样的感觉?让我们继续。
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 11 , b = 111111;
short* p = (short*)&a;
printf("a_s:%d\n",*p);
p = (short*)&b;
printf("b_s:%d\n",*p);
return 0 ;
}
- 我们同样先来看看输出结果
a_s:11
b_s:-19961
- 结果似乎也在意料之中,毕竟b的值超出了short可以储存的范围,但是到底是为什么呢,我们用位域来研究一下这个问题
#include <stdio.h>
struct num{
short a:1,b:1,c:1,d:1,e:1,f:1,g:1,h:1,i:1,j:1,k:1,l:1,m:1,n:1,o:1,p:1;
};
int main(int argc, char const *argv[])
{
struct num a;
a.a = 1;
a.b = 1;
a.c = 1;
a.d = 1;
a.e = 1;
a.f = 1;
a.g = 1;
a.h = 1;
a.i = 1;
a.j = 1;
a.k = 1;
a.l = 1;
a.m = 1;
a.n = 1;
a.o = 1;
a.p = 0;
short* sp = &a;
printf("p:%d\n",*sp);
char* cp = &a;
printf("sp:%d\n",*cp);
return 0 ;
}
- 我们还是先来看看输出
p:32767
sp:-1
-
32767的二进制是0111 1111 1111 1111,那么-1是1111 1111为什么1111 1111是-1而不是255呢,这就是最左边的一位可以看作是符号位,所以1111 1111其实是-1的补码,原码实际上是1000 0001,这样印证了计算机是以补码形式存储数据的,所以我们可以很直观的看到cp指针打印的只是short类型2个字节中的一个字节,而且是低8位,看到这里,是不是对指针有点更加不一样的感觉了呢?至于那个-19961的问题大家可以自己去研究一下。
-
到这里我们就可以做到为某一个类型手动指定栈区的大小了,尤其是结构体类型,像下面这样
#include <stdio.h>
struct a{};
int main(int argc, char const *argv[])
{
char buffer[128];
struct a* sa = (struct a*)buffer;
}
- 关于C语言指针其实还有很多奇妙的东西,关键其实就两个字,瞎搞!