本文主要介绍C语言函数指针的知识。这里编译了详细的信息和示例代码,供您参考。需要学习这部分知识的朋友可以参考一下。
目录
函数指针简介函数指针练习qsort中的函数指针练习2:摘要
Introduction
最后一个实验室的主要内容是__segmentation fault__可能是Linux系统中__data pointer__(指向数据的指针)导致的。这次lab会考虑__function pointer__(指向函数/代码的指针):segfault或者其他异常可能导致的错误。
函数指针 Function Pointers
函数指针可以像函数一样被调用,包括传递参数和获取返回结果。函数指针的一些用途是编写通用的泛型函数,有时是一种面向对象的风格,以及实现回调。
在函数中,有可以赋给指针的物理内存地址,函数的函数名是指向函数代码的指针;
函数的地址是函数的入口点和调用函数的地址;
函数可以通过函数名或指向函数的指针来调用;
函数指针还允许函数作为参数传递给其他函数;
不带括号的函数名和变量列表也可以表示函数的地址(在数组中,不带下标的数组名表示数组的第一个地址)。
定义形式
Type (*指针变量名)(参数列表);
例如int (*p)(int i,int j);
p是一个点;er,指向一个带有两个整型参数,返回类型为int的函数;先将P与*组合,表示P是指针,再与()组合,表示指向函数。
调用函数指针
(*p)(论点)
p(自变量)
例子
#包含stdio.h
#define GET_MAX 0
#定义GET_MIN 1
int get_max(int i,int j)
{
返回ij?I:j;
}
int get_min(int i,int j)
{
返回ij?j:我;
}
int compare(int i,int j,int flag)
{
int ret
//这里定义了一个函数指针,可以根据传入标志灵活决定是指向查找大数还是小数的函数。
//调用各种函数方便灵活。
int (*p)(int,int);
if(flag==GET_MAX)
p=get _ max
其他
p=get _ min
ret=p(i,j);
返回ret
}
int main()
{
int i=5,j=10,ret
ret=compare(i,j,GET _ MAX);
printf('最大值为%d\n ',ret);
ret=compare(i,j,GET _ MIN);
printf('最小值为%d\n ',ret);
返回0;
}
#包含stdio.h
#包含字符串. h
void check(char *a,char *b,int (*cmp)(const char *,const char *));
主()
{
char s1[80],S2[80];
int (*p)(const char *,const char *);
//将库函数strcmp的地址赋给函数指针p。
p=strcmp
printf('输入两个字符串。\ n’);
gets(S1);
获取(S2);
检查(s1,s2,p);
}
void check(char *a,char *b,int (*cmp)(const char *,const char *))
{
printf('相等测试。\ n’);
//表达式(*cmp)(a,B)调用strcmp,cmp指向库函数strcmp(),以A和B为参数调用strcmp()。
//调用时,类似于声明的情况,必须在*cmp周围使用一对括号,使编译器正确运行,
//同时,这也是一种很好的编码风格。指示器函数是由指针而不是函数名调用的。
if((*cmp)(a,b)==0)
printf(' Equal \ n ');
其他
printf('不等于\ n ');
}
int *f(int i,int j);
int (*p)(int i,int j);
前者是返回值是指针的函数;后者是指向函数的指针。
注意,这个实验中用到的技术也会出现在JIT编译器中(比如浏览器中的JavaScript编译器),因为它们也是把数据转换成代码,然后调用它。请注意,试图执行代码的攻击可能会使用本实验的变体。这个实验也说明了一个常见的错误,可能是不确定的。
Exercise 1:qsort中的函数指针
q s o r t () qsort() qsort()为什么要用函数指针?
q s o r t ( ) qsort() qsort()是对任何类型的数组数据的通用排序例程(通用分类程序),因此,不同的数据类型需要不同的比较方法,这是由用户提供的比较函数比较比较比较提供的。简单说就是对数组进行排序。
声明:
void qsort(void *base,size_t nitems,size_t size,int (*compar)(const void *,const void *))
参数:
基础指向要排序的数组的第一个元素的指针。
nitems由基础指向的数组中元素的个数。
尺寸数组中每个元素的大小,以字节为单位。
比较用来比较两个元素的函数。如果比较返回值0,那么第一个参数第一亲代所指向元素会被排在第二个p2所指向元素的前面;如果比较返回值=0,那么第一亲代所指向元素与p2所指向元素的顺序不确定;如果比较返回值0,那么第一亲代所指向元素会被排在p2所指向元素的后面。
返回值 无
代码
#包含标准库
#包含字符串。h
#包含字符串。h
#包含标准视频
//对比较函数(*比较)()使用函数指针的qsort示例
//参见男子qsort
char *num[]={
'000000001',
'1',
'1000',
' 100 ',
'1 ',
};
//将p1 p2作为字符串进行比较
//用数组元素的地址调用,例如(char *)=char **
int string_comp(常量void *p1,常量void *p2)
{
//注意传递元素的地址,这样就有了
//这里需要额外的*,因为它已经是(char *)
//返回strcmp((char *) p1,(char *)p2);/*一个需要的bug deref */
返回strcmp(*((char **) p1),*((char * *)p2));
}
//将p1 p2作为整数进行比较
int int_comp(常数void *p1,常数void *p2)
{
int i1,I2;
//i1=atoi((char *)P1);/*错误:与string_comp() */中的线条原因相同
i1=atoi(*((char * *)P1));/*正确*/
//I2=atoi((char *)p2);/*错误:与string_comp() */中的线条原因相同
I2=atoi(*((char * *)p2));/*正确*/
//printf('comp(%s,%s)\n ',p1,p2);/*错误:用于调试*/
//printf('comp('%s ',' %s')\n ',*(char **) p1,*(char * *)p2);/*正确:用于调试*/
if (i1 i2)返回-1;
else if (i1==i2)返回0;
否则返回1;
}
void print_array(char *a[],int n)
{
int I;
for(I=0;I n;我)
printf("% s ",a[I]);
}
int main()
{
printf('原始数组\ n’);print_array(num,5);printf(' \ n ');
qsort(num,5,sizeof(char *),int _ comp);
printf(' sorted array as int \ n ');print_array(num,5);printf(' \ n ');
qsort(num,5,sizeof(char *),string _ comp);
printf('以字符串形式排序的数组\ n’);print_array(num,5);printf(' \ n ');
return(0);
}
一般形式:strcmp(字符串1,字符串2)
说明:当s1s2时,返回为负数注意不是-1
当s1==s2时,返回值=0
当s1s2时,返回正数注意不是一
即:两个字符串自左向右逐个字符相比(按美国信息交换标准代码值大小相比较),直到出现不同的字符或遇'\0'为止。如:
" A""B" "a""A " "计算机" "比较"
特别注意:strcmp(常量字符*s1,常量字符* s2)这里面只能比较字符串,不能比较数字等其他形式的参数。
Exercise 2:
代码:
#包含标准视频
#包含字符串。h
#包含字符串。h
#包含错误号h
#include sys/mman.h
#定义第32条
int addnum(int a)
{
返回一个255;
}
int main(int argc,char *argv[],char *envp[])
{
int a,(*f)(int),* code _ buf
char *p,data[]={0x0f,0x0b };
/*尝试取消注释*/
/*
f=空;
a=f(10);
printf('0: f(10)=%d f=%p\n\n ',a,f);
*/
f=addnum
a=f(10);//LINE1
printf('1: f(10)=%d f=%p\n\n ',a,f);
code_buf=(int *) mmap(0,4096,PROT_EXEC|PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
/*尝试交换两条mmap线*/
//code_buf=(int *) mmap(0,4096,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANONYMOUS,-1,0);
printf('code_buf %p\n ',code _ buf);
memcpy(code_buf,addnum,L);
f=(int(*)(int))code _ buf;
a=f(10);//第2行
printf('2: f(10)=%d f=%p\n\n ',a,f);
p=index((char *) code_buf,0x ff);
printf('before *p=%hhx\n ',* p);
* p=100
printf('after *p=%hhx\n ',* p);
a=f(10);//第3行
printf('3: f(10)=%d\n\n ',a);
*((char *)code _ buf)=0x C3;
a=f(10);//第4行
printf('4: f(10)=%d\n\n ',a);
memcpy(code_buf,data,2);
printf('上次调用前\ n ');
a=f(10);//第5行
返回0;
}
mmap()函数:
用途:
将一个普通文件映射到内存中,通常是在需要频繁读写文件的时候使用,这样I/O读写就被内存读写代替了,以获得更高的性能;
特殊文件的匿名内存映射可以为关联进程提供共享内存空间;
为了给不相关的进程提供共享的内存空间,通常将一个公共文件映射到内存中。
头文件:#include sys/mman.h
原型:void * mmap (void * start,size _ tlength,intprot,intflags,intfd,off _ toff size);
参数描述:
Start:指要映射的内存起始地址,通常设置为NULL,表示系统会自动选择地址,映射成功后返回地址。
Length:表示有多少文件被映射到内存中。
Prot:映射区域的保护方法。它可以是以下方式的组合:
可以执行PROT_EXEC的映射区域。
可以读取PROT _里德映射区
PROT _写映射区可以被写
无法访问PROT _无映射区域
标志:影响映射区域的各种特征。调用mmap()时必须指定MAP_SHARED或MAP_PRIVATE。
MAP_FIXED:如果由参数start指示的地址不能被成功映射,映射将被放弃,并且地址将不会被纠正。通常不鼓励使用此标志。
MAP_SHARED:写入映射区域的数据将被复制回文件,并由映射该文件的其他进程共享。
MAP_PRIVATE:写入映射区域将产生映射文件的副本,也就是说,私有副本在写入时对该区域所做的任何修改都不会被写回原始文件内容。
MAP_ANONYMOUS:建立匿名映射。此时,参数fd被忽略,不涉及任何文件,并且映射区域不能与其他进程共享。
MAP_DENYWRITE:只允许对映射区域进行写操作,其他对文件的直接写操作将被拒绝。
MAP_LOCKED:映射的区域被锁定,这意味着该区域不会被交换。
Fd:映射到内存的文件描述符。如果使用匿名内存映射,即在标志中设置MAP_ANONYMOUS,则fd设置为-1。
Offset:文件映射的偏移量,通常设置为0,表示从文件前面开始对应。偏移量必须是页面大小的整数倍。
返回值:如果映射成功,_ _返回映射区_ _的内存起始地址,否则返回map _ failed (-1),错误原因存储在errno中。
错误代码:
EBADF参数fd不是有效的文件描述符。
EACCES的访问权限出错。如果是M
在P_PRIVATE的情况下,文件必须是可读的,而在MAP_SHARED的情况下,必须有PROT_WRITE并且文件必须是可写的。
EIN参数start、length或offset之一是非法的。
EAGAIN文件被锁定,或者锁定了太多内存。
ENOMEM内存不足。
Memcpy()函数:
目的:记忆复制。
原型:void * memcpy (void * dest,constvoid * src,size _ t n);
功能:将src开头的N字节数据复制到dest。如果dest中有数据,它将被覆盖。
返回值:指向目标的指针。
头文件:string.h
Index()函数:
目的:寻找地址。
原型:char * index(const char *s,int c);
函数:用于查找参数S字符串中第一个参数C的地址,然后返回该字符出现的地址。字符串的结束字符(NULL)也被视为字符串的一部分。返回值:如果找到指定的字符,则返回该字符的地址;否则,返回0。
头文件:# includestring.exercise3
代码:
#包含stdio.h
#包含字符串. h
#包含字符串. h
#包含错误号h
#include sys/mman.h
int encrypt(int a) //使用(secret)常量的简单加密
{
归还一辆^ 255;
}
int main(int argc,char *argv[],char *envp[])
{
int(* f)(int);
int秘密,x,y;
//测试加密
f=加密;
printf('1:输入测试数据\ n ');
scanf('%d ',x);
y=f(x);
printf(' 1:test original encrypt():f(% x)=% x \ n ',x,y);
printf('输入新密钥\ n ');
scanf('%d ',secret);
//使用encrypt的代码字节作为模板创建一个新函数
//它类似于encrypt(),只是它与秘密进行异或运算
//已经输入的键
//让f指向下面代码创建的新函数
//LINE1
/*您的代码在这里*/
//第2行
秘密=255;//删除机密,将其设置回原始密钥
//测试是否有效
printf('2:要加密的值\ n ');
scanf('%d ',x);
y=f(x);//应该使用上面的输入密钥
printf('2: f(): f(%x)=%x\n ',x,y);
printf('已擦除的机密%d\n ',secret);
//测试f()是否可修改
*((int *)f)=0;//LINE3:此处必须segfault
返回0;
}
要求:
加密方法仅使数据异或或密钥保密)。原来的密码是255。
目标:从源代码中去除密码的依赖性,即使有人知道源代码,无法知道密码(只考虑整数)。
只修改第1行和第2行之间的代码。
限制:
您不能使用新功能或新软件包。
需要在运行时动态创建f()指向的新代码。(F随输入变化)
新函数实现了与encrypt()相同的函数,只是常量来自输入(读为secret)。
加密是密码的异或运算,形式为(A C),其中C是常数而不是C变量。
另外,虽然f()是在运行时动态创建的,但在使用时不应该修改。在第3行测试中,出现segfault。提示:看看mprotect,它是mmap的补充。
总结
本文到此为止。希望能帮到你,也希望你能多关注我们的更多内容!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。