1. 前言
最近工作中,在干一件事情,就是打通从安卓的java代码,到JNI,最后到server端的C代码。
当然这个过程,早已有大神们做好了,我只是根据当前的业务需要,定制化的实现这个过程。
那这和函数指针,有啥关系呢?请先看程序和内存的关联关系。
2. C语言中的内存布局
C语言的内存布局[1], 如下图所示:
任何一个C语言可执行程序,以有组织的方式加载到计算机内存当中,这段内存被称为进程地址空间或者C程序的内存布局。
通常,将这个内存布局,分为如下几个部分:
(1)Text segment ——文本段
文本段包含C程序的可执行指令,也称为代码段。文本段是不同的进程之间可共享的。
(2)Data segment —— 数据段
这里分为2个子段:初始化数据段和未初始化数据段。通俗的说,不为0的全局变量和静态变量,会存储在初始化数据段。其余的变量会存储在未初始化数据段。
(3)Heap segment —— 堆
在堆区,我们可以动态的开辟和释放内存。开辟内存可以使用malloc(), calloc(), realloc() and new for C++。释放内存可以使用free() or delete。
(4)Stack segment —— 栈
所有函数当中声明的变量,会存储在栈区,存储内容也包括地址,形参等信息。当我们调用某个函数,会创建一个stack frame,当函数返回之后,stack frame将会被销毁。stack pointer将会记录所有的出栈,入栈信息。
每一个函数都拥有自己的stack frame,当调用函数时,stack frame的结构图如下:
(5)Unmapped or reserved —— 预留段
未映射或预留段包含命令行参数和其他程序相关数据,如可执行映像的较低地址,较高地址等。
3. 函数指针
(1)函数指针:指向函数地址的指针。
(A function pointer is a pointer that refers to the address of a function.)
一个典型的函数指针,如下:
int (*func)(int a, float b);
(2)函数指针的例子[2]——异步调用
demo1 :
/ * This code catches the alarm signal generated from the kernel Asynchronously */ #include <stdio.h> #include <signal.h> #include <unistd.h> struct sigaction act; /* signal handler definition goes here */ void sig_handler(int signo, siginfo_t *si, void *ucontext) { printf("Got alarm signal %d\n",signo); /* do the required stuff here */ } int main(void) { act.sa_sigaction = sig_handler; act.sa_flags = SA_SIGINFO; /* register signal handler */ sigaction(SIGALRM, &act, NULL); /* set the alarm for 10 sec */ alarm(10); /* wait for any signal from kernel */ pause(); /* after signal handler execution */ printf("back to main\n"); return 0; }
demo2:
/* insertion_sort.h */ typedef int (*callback)(int, int); void insertion_sort(int *array, int n, callback comparison);
/* insertion_main.c */ #include<stdio.h> #include<stdlib.h> #include"insertion_sort.h" int ascending(int a, int b) { return a > b; } int descending(int a, int b) { return a < b; } int even_first(int a, int b) { /* code goes here */ } int odd_first(int a, int b) { /* code goes here */ } int main(void) { int i; int choice; int array[10] = {22,66,55,11,99,33,44,77,88,0}; printf("ascending 1: descending 2: even_first 3: odd_first 4: quit 5\n"); printf("enter your choice = "); scanf("%d",&choice); switch(choice) { case 1: insertion_sort(array,10, ascending); break; case 2: insertion_sort(array,10, descending); case 3: insertion_sort(array,10, even_first); break; case 4: insertion_sort(array,10, odd_first); break; case 5: exit(0); default: printf("no such option\n"); } printf("after insertion_sort\n"); for(i=0;i<10;i++) printf("%d\t", array[i]); printf("\n"); return 0; }
/* insertion_sort.c */ #include"insertion_sort.h" void insertion_sort(int *array, int n, callback comparison) { int i, j, key; for(j=1; j<=n-1;j++) { key=array[j]; i=j-1; while(i >=0 && comparison(array[i], key)) { array[i+1]=array[i]; i=i-1; } array[i+1]=key; } }
4. 再探内存地址和程序的关系从以上2个demo,已经可以很明显的看出函数指针的用途和用法。
不过,这和开始说的,函数指针是一切的根基,好像毫无关联啊,怎么看都是一种编程技巧嘛。
别急,往下看。
有人曾经说过,c语言的精华是指针,我认为这句话没有说完,c语言的精华是函数指针才对。
为什么这么说?
既然,程序只不过是内存的一部分。而函数是程序的一部分。
那换句话说,其实程序就是计算机按照一定顺序访问内存地址的过程。
那么,只要能够控制(获取)内存地址,就等于掌握了计算机。
说到这里,函数指针的意义已经很明确了。
那么,安卓的java代码,与JNI的C代码,再到server的C代码,到底怎么交互呢?
很简单,互相传递函数指针,一切搞定!
参考文献:
[1]http://www.firmcodes.com/memory-layout-c-program-2/
[2]http://opensourceforu.com/2012/02/function-pointers-and-callbacks-in-c-an-odyssey/