C语言函数指针详解

目录
  • Introduction
  • 函数指针 Function Pointers
  • Exercise 1:qsort中的函数指针
  • Exercise 2:
  • 总结

Introduction

上一个lab的主要内容为__data pointer__(指向数据的指针)可能在Linux系统中造成的__segmentation fault__。本次lab将考虑__function pointer__(指向函数/代码的指针)可能造成的错误:segfault或其他exceptions。

函数指针 Function Pointers

一个函数指针可以像函数一样被调用,包括传递参数和获得返回结果。函数指针的一些用途是用于编写泛型generic函数,有时是一种面向对象的样式,也用于实现回调。

  • 在函数中,有物理内存地址可以赋值给指针,而一个函数的函数名就是一个指针,指向函数的代码;
  • 一个函数的地址就是该函数的进入点,也是调用函数的地址;
  • 函数的调用可以通过函数名,也可以通过指向函数的指针;
  • 函数指针还允许将函数作为变元传递给其他函数;
  • 没有括号和变量列表的函数名也可以表示函数的地址(数组中,不带下标的数组名表示数组的首地址)

定义形式

类型 (*指针变量名) (参数列表);

如, int (*p)(int i, int j);

→ p是一个指针,它指向一个函数,该函数有两个整型参数,返回类型为int;p首先和*结合,表明p是一个指针,再与( )结合,表明它指向的是一个函数。

调用函数指针

(*p) (argument)

p (argument)

例子

#include <stdio.h>
#define  GET_MAX 	0
#define  GET_MIN 	1
int get_max(int i,int j)
{
	return i>j?i:j;
}
int get_min(int i,int j)
{
	return i>j?j:i;
}
int compare(int i,int j,int flag)
{
	int ret;
	//这里定义了一个函数指针,就可以根据传入的flag,灵活地决定其是指向求大数或求小数的函数
	//便于方便灵活地调用各类函数
	int (*p)(int,int);
	if(flag == GET_MAX)
		p = get_max;
	else
		p = get_min;
	ret = p(i,j);
	return ret;
}
int main()
{
	int i = 5,j = 10,ret;
	ret = compare(i,j,GET_MAX);
	printf("The MAX is %d\n",ret);
	ret = compare(i,j,GET_MIN);
	printf("The MIN is %d\n",ret);
	return 0 ;
}
#include <stdio.h>
#include <string.h>
void check(char *a,char *b,int (*cmp)(const char *,const char *));
main()
{
    char s1[80],s2[80];
    int (*p)(const char *,const char *);
	//将库函数strcmp的地址赋值给函数指针p
    p=strcmp;
    printf("Enter two strings.\n");
    gets(s1);
    gets(s2);
    check(s1,s2,p);
}
void check(char *a,char *b,int (*cmp)(const char *,const char *))
{
    printf("Testing for equality.\n");
	//表达式(*cmp)(a,b)调用strcmp,由cmp指向库函数strcmp(),由a和b作调用strcmp()的参数。
	//调用时,与声明的情况类似,必须在*cmp周围使用一对括号,使编译程序正确操作,
	//同时这也是一种良好的编码风格,指示函数是通过指针调用的,而不是函数名。
    if((*cmp)(a,b)==0)
        printf("Equal\n");
    else
        printf("Not Equal\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() 是对任何类型的数组数据的通用排序例程(generic sorting routine ),因此,不同的数据类型需要不同的比较方法,这是由用户提供的比较函数 c o m p a r e ( ) compare() compare() 提供的。简单说就是对数组进行排序。
    • 声明:

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

  • 参数:

    • base – 指向要排序的数组的第一个元素的指针。
    • nitems – 由 base 指向的数组中元素的个数。
    • size – 数组中每个元素的大小,以字节为单位。
    • compar – 用来比较两个元素的函数。如果 compar 返回值< 0,那么第一个参数p1 所指向元素会被排在第二个p2所指向元素的前面;如果 compar 返回值= 0,那么 p1 所指向元素与 p2 所指向元素的顺序不确定;如果 compar 返回值> 0,那么 p1 所指向元素会被排在 p2 所指向元素的后面。
    • 返回值 – 无

代码

#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <stdio.h>
// example of qsort using function pointer for comparison function (*compar)()
// see man qsort
char *num[] = {
	"000000001",
	"1",
	"1000",
	"  100 ",
	"1     ",
};

// compare p1 & p2 as strings
// call with the address of the array element, e.g. &(char *) = char **
int string_comp(const void *p1, const void *p2)
{
	// be careful that address of element is passed so there is an
	// extra * needed here given that it is already (char *)
	// return strcmp((char *) p1, (char *) p2); /* a bug as needs deref */
	return strcmp(*((char **) p1), *((char **) p2));
}
// compare p1 & p2 as integers
int int_comp(const void *p1, const void *p2)
{
	int i1, i2;
	// i1 = atoi((char *) p1); /* bug: same reason as line in string_comp() */
	i1 = atoi(*((char **) p1)); /* correct */
	// i2 = atoi((char *) p2); /* bug: same reason as line in string_comp() */
	i2 = atoi(*((char **) p2)); /* correct */
	// printf("comp(%s,%s)\n", p1, p2); /* bug: for debugging */
	// printf("comp(\"%s\", \"%s\")\n", *(char **) p1, *(char **) p2); /* correct: for debugging */
	if (i1 < i2) return -1;
	else if (i1 == i2) return 0;
	else return 1;
}
void print_array(char *a[], int n)
{
	int i;
	for (i=0; i < n; i++)
		printf("\"%s\" ", a[i]);
}
int main()
{
	printf("Original array\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("sorted array as string\n"); print_array(num, 5); printf("\n");
	return(0);
}

一般形式:strcmp(字符串1,字符串2)

说明:当s1<s2时,返回为负数 注意不是-1

当s1==s2时,返回值= 0

当s1>s2时,返回正数 注意不是1

即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。如:

“A”<“B” “a”>“A” “computer”>“compare”

特别注意:strcmp(const char *s1,const char * s2)这里面只能比较字符串,不能比较数字等其他形式的参数。

Exercise 2:

代码:

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/mman.h>
#define L (32)
int addnum(int a)
{
	return a+255;
}
int main(int argc, char *argv[], char *envp[])
{
	int a, (*f)(int), *code_buf;
	char *p, data[]={0x0f, 0x0b};
	/* Try uncommenting out */
	/*
	f = NULL;
	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);
	/* Try swapping the two mmap lines */
	// 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); // LINE2
	printf("2: f(10)=%d f=%p\n\n", a, f);
	p = index((char *) code_buf, 0xff);
	printf("before *p=%hhx\n", *p);
	*p = 100;
	printf("after *p=%hhx\n", *p);
	a = f(10); // LINE3
	printf("3: f(10)=%d\n\n", a);
	*((char *) code_buf) = 0xc3;
	a = f(10); // LINE4
	printf("4: f(10)=%d\n\n", a);
	memcpy(code_buf, data, 2);
	printf("before last call\n");
	a = f(10); // LINE5
	return 0;
}
  • mmap() 函数:

    • 用途:

      • 将一个普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,这样用内存读写取代I/O读写,以获得较高的性能;
      • 将特殊文件进行匿名内存映射,可以为关联进程提供共享内存空间;
      • 为无关联的进程提供共享内存空间,一般也是将一个普通文件映射到内存中。
    • 头文件:#include <sys/mman.h>
    • 原型:void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
    • 参数说明:
      • start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。
      • length:代表将文件中多大的部分映射到内存。
      • prot:映射区域的保护方式。可以为以下几种方式的组合:
        • PROT_EXEC 映射区域可被执行
        • PROT_READ 映射区域可被读取
        • PROT_WRITE 映射区域可被写入
        • PROT_NONE 映射区域不能存取
      • flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。
        • MAP_FIXED:如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。
        • MAP_SHARED:对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。
        • MAP_PRIVATE:对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。
        • MAP_ANONYMOUS:建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。
        • MAP_DENYWRITE:只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。
        • MAP_LOCKED:将映射区域锁定住,这表示该区域不会被置换(swap)。

fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。

offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值:若映射成功则__返回映射区的内存起始地址__,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:

EBADF 参数fd 不是有效的文件描述词

EACCES 存取权限有误。如果是M

P_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。

EINVAL 参数start、length 或offset有一个不合法。

EAGAIN 文件被锁住,或是有太多内存被锁住。

ENOMEM 内存不足。

  • memcpy() 函数:

    • 用途:内存复制。
    • 原型:void *memcpy(void *dest, const void *src, size_t n);
    • 功能:从src的开始位置拷贝n个字节的数据到dest。如果dest存在数据,将会被覆盖。
    • 返回值:dest的指针。
    • 头文件:string.h
  • index() 函数:
    • 用途:找地址。
    • 原型:char * index(const char *s, int c);
    • 功能:用来找出参数s 字符串中第一个出现的参数c 地址,然后将该字符出现的地址返回。字符串结束字符(NULL)也视为字符串一部分。返回值:如果找到指定的字符则返回该字符所在地址,否则返回0。
    • 头文件:#include <string.h> Exercise 3

代码:

#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/mman.h>
int encrypt(int a) // simple encryption with a (secret) constant
{
	return a ^ 255;
}
int main(int argc, char *argv[], char *envp[])
{
	int (*f)(int);
	int secret, x, y;
	// test encrypt
	f = encrypt;
	printf("1: enter test data\n");
	scanf("%d", &x);
	y = f(x);
	printf("1: test original encrypt(): f(%x)=%x\n", x, y);
	printf("enter new key\n");
	scanf("%d", &secret);
	// create a new function by using encrypt's code-bytes as a template
	// which is similar to encrypt() except that it is xor with the secret
	// key which has been input
	// let f point to the new function created by the code below
	// LINE1
	/* your code goes here */
	// LINE2
	secret = 255; // erase secret, set it back to the original key
	// test if it works
	printf("2: value to encrypt\n");
	scanf("%d", &x);
	y = f(x); // should use the input secret key above
	printf("2: f(): f(%x)=%x\n", x, y);
	printf("erased secret %d\n", secret);
	// test if f() is modifiable
	*((int *)f) = 0; // LINE3: must segfault here
	return 0;
}
  • 要求:

    • 加密方法只是让数据XOR亦或整常数密码(key,secret)。原始密码已知,为255。
    • 目标:从源代码中移除密码的依赖性,尽管有人知道源代码也无法知道密码(只考虑整数)。
    • 只修改LINE1和LINE2之间的代码。
  • 限制:
    • 不能使用新函数或者新包。
    • 需要在运行时,动态地创建f()所指向的新代码。(f是随输入变化的)
    • 新函数实现了与encrypt()相同的功能,除了常量来自输入(读入为secret)。
    • 加密是密码的异或,采用(a ^ c)形式,其中c是常数而不是c变量。
    • 此外,尽管f()是在运行时动态创建的,但在使用时不应该修改。在LINE3测试,即出现segfault。 hint:看看mprotect,它补充了mmap

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • C语言函数的参数使用指针

    在c语言中实参和形参之间的数据传输是单向的"值传递"方式,也就是实参可以影响形参,而形参不能影响实参.指针变量作为参数也不例外,但是可以改变实参指针变量所指向的变量的值. #include <stdio.h> void swap1(int x,int y),swap2(int *px,int *py),swap3(int *px,int *py); int main(void) { int a=1,b=2; int *pa=&a,*pb=&b; swap1(

  • C语言中函数指针的三种使用方法总结

     C语言中函数指针的三种使用方法总结 在这里分享一下自己的心得,希望和大家一起分享技术,如果有什么不足,还请大家指正.写出这篇目的,就是希望大家一起成长,我也相信技术之间没有高低,只有互补,只有分享,才能使彼此更加成长. 定义方式:int (*p)(int x, int y); 实现代码: #include <stdio.h> int sum(int x, int y){ return x + y; } int reduce(int x, int y){ return x - y; } int

  • C语言中函数指针与软件设计经验总结

    函数指针与软件设计 记得刚开始工作时,一位高手告诉我,说,longjmp和setjmp玩得不熟,就不要自称为C语言高手.当时我半信半疑,为了让自己向高手方向迈进,还是花了一点时间去学习longjmp和setjmp的用法.后来明白那不单是跳来跳去那样简单,而是一种高级的异常处理机制,在某些情况下确实很有用. 为了显示自己的技巧,也在自己的程序中用过几次.渐渐发现这样的技巧带来的好处是有代价的,破坏了程序的结构化设计,程序变得很难读,尤其对新手来说.终于明白这种技巧不过是一种调味料,在少数情况使用几

  • C语言 指针变量作为函数参数详解

    在C语言中,函数的参数不仅可以是整数.小数.字符等具体的数据,还可以是指向它们的指针.用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁. 像数组.字符串.动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些数据集合. 有的时候,对于整数.小数.字符等基本类型数据的操作也必须要借助指针,一个典型的例子就是交换两个变量的值. 有些初学者可能会使用

  • 详解C语言中的函数、数组与指针

    1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:   函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成.这就是程序设计的基本分化方法: (1) 写一个函数的关键: 函数定义:函数的定义是这个函数的实现,函数定义中包含了函数体,函数体中的代码段决定了这个函数的功能: 函数声明:函数声明也称函数原型声明,函数的原

  • C语言 函数指针(指向函数的指针)详解

    一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似.我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数.这种指针就是函数指针. 函数指针的定义形式为: returnType (*pointerName)(param list); returnType 为函数返回值类型,pointerNmae 为指针名称,param list 为函数参数列表.参数列表中

  • C语言 用指针作为函数返回值详解

    C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数.下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个: #include <stdio.h> #include <string.h> char *strlong(char *str1, char *str2){ if(strlen(str1) >= strlen(str2)){ return str1; }else{ return str2; } } int main(){ cha

  • C语言函数指针详解

    目录 Introduction 函数指针 Function Pointers Exercise 1:qsort中的函数指针 Exercise 2: 总结 Introduction 上一个lab的主要内容为__data pointer__(指向数据的指针)可能在Linux系统中造成的__segmentation fault__.本次lab将考虑__function pointer__(指向函数/代码的指针)可能造成的错误:segfault或其他exceptions. 函数指针 Function P

  • C++中函数指针详解及代码分享

    函数指针 函数存放在内存的代码区域内,它们同样有地址.如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,如同数组的名字就是数组的起始地址. 1.函数指针的定义方式:data_types (*func_pointer)( data_types arg1, data_types arg2, ...,data_types argn); c语言函数指针的定义形式:返回类型 (*函数指针名称)(参数类型,参数类型,参数类型,-); c++函数指针的定义形式:返回类型 (类名

  • C++获取类的成员函数的函数指针详解及实例代码

    C++获取类的成员函数的函数指针详解 用一个实际代码来说明. class A { public: staticvoid staticmember(){cout<<"static"<<endl;} //static member void nonstatic(){cout<<"nonstatic"<<endl;} //nonstatic member virtualvoid virtualmember(){cout<

  • C++函数指针详解

    函数指针基础: 1. 获取函数的地址 2. 声明一个函数指针 3.使用函数指针来调用函数 获取函数指针: 函数的地址就是函数名,要将函数作为参数进行传递,必须传递函数名. 声明函数指针 声明指针时,必须指定指针指向的数据类型,同样,声明指向函数的指针时,必须指定指针指向的函数类型,这意味着声明应当指定函数的返回类型以及函数的参数列表. 例如: double cal(int); // prototype double (*pf)(int); // 指针pf指向的函数, 输入参数为int,返回值为d

  • C语言进阶教程之函数指针详解

    目录 一.函数指针 1.概念 1.2函数指针的使用方法 1.3练习巩固 1.4小结一下 二.阅读两段有趣的代码 1.( *(void( *)( ))0 )( ) 2.void (* signal(int,void( * )( int ) ) )(int) 附:函数指针的应用——函数回调 总结 一.函数指针 1.概念 函数指针:首先它是一个指针,一个指向函数的指针,在内存空间中存放的是函数的地址: 请看示例: int main(){ int a = 10; int*pa = &a; char ch

  • C语言的数组指针与函数指针详解

    目录 前言 函数指针语法 数组指针与指针数组 总结 前言 数组指针和函数指针都是C语言比较难的知识点,尤其是函数指针,并且函数指针在开发中有着巨大的作用. 函数指针语法 定义一个函数指针,并通过函数指针间接调用函数: int get_num(int a, int b) { return a + b; } int (*func)(int a, int b); //定义了一个函数指针func,它指向 返回值为int 参数为 int a, int b的函数 func = &get_num; //函数指

  • C语言 二级指针详解及示例代码

    指针可以指向一份普通类型的数据,例如 int.double.char 等,也可以指向一份指针类型的数据,例如 int *.double *.char * 等. 如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针. 假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示: 将这种关系转换为C语言代码: int a =100; int *p1 = &a; int **p2 = &p1; 指针变量也是一种变量

  • C语言 数组指针详解及示例代码

    数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element).数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存.以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示: 定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素.在C语言中,我们将第 0 个元素的地址称为数组的首地址.以上面的数组为例,下图是 arr 的指向: 下面的例子演示了如何以指针的方

  • C语言 字符串指针详解及示例代码

    C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中,这在<C语言字符数组和字符串>中已经进行了详细讲解,这里不妨再来演示一下: #include <stdio.h> int main(){ char str[] = "http://c.biancheng.net"; int len = strlen(str), i; //直接输出字符串 printf("%s\n", str); //每次输出一个字符 for(i=0; i<

  • C语言基础指针详解教程

    目录 1.1:概述 1.1.1:内存 1.1.2:内存 1.1.3:指针和指针变量 1.2:指针基础知识 1.2.1:指针变量的定义和使用 1.2.2:通过指针间接修改变量的值 1.2.3:指针的大小 1.2.4:空指针与野指针 1.2.4:万能指针 1.2.5:const修饰的指针变量 1.3:指针与数组 1.3.1:数组名 1.3.2:指针操作数组 1.3.3:指针的加减运算 1.3.4:指针数组 1.4:指针基础小结 1.5:结语 1.1:概述 1.1.1:内存 内存含义: 储存器:用来存

随机推荐