C語言函數(shù)指針詳解
Introduction
上一個lab的主要內(nèi)容為__data pointer__(指向數(shù)據(jù)的指針)可能在Linux系統(tǒng)中造成的__segmentation fault__。本次lab將考慮__function pointer__(指向函數(shù)/代碼的指針)可能造成的錯誤:segfault或其他exceptions。
函數(shù)指針 Function Pointers
一個函數(shù)指針可以像函數(shù)一樣被調(diào)用,包括傳遞參數(shù)和獲得返回結(jié)果。函數(shù)指針的一些用途是用于編寫泛型generic函數(shù),有時是一種面向?qū)ο蟮臉邮?,也用于實現(xiàn)回調(diào)。
- 在函數(shù)中,有物理內(nèi)存地址可以賦值給指針,而一個函數(shù)的函數(shù)名就是一個指針,指向函數(shù)的代碼;
- 一個函數(shù)的地址就是該函數(shù)的進入點,也是調(diào)用函數(shù)的地址;
- 函數(shù)的調(diào)用可以通過函數(shù)名,也可以通過指向函數(shù)的指針;
- 函數(shù)指針還允許將函數(shù)作為變元傳遞給其他函數(shù);
- 沒有括號和變量列表的函數(shù)名也可以表示函數(shù)的地址(數(shù)組中,不帶下標(biāo)的數(shù)組名表示數(shù)組的首地址)
定義形式
類型 (*指針變量名) (參數(shù)列表);
如, int (*p)(int i, int j);
→ p是一個指針,它指向一個函數(shù),該函數(shù)有兩個整型參數(shù),返回類型為int;p首先和*結(jié)合,表明p是一個指針,再與( )結(jié)合,表明它指向的是一個函數(shù)。
調(diào)用函數(shù)指針
(*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; //這里定義了一個函數(shù)指針,就可以根據(jù)傳入的flag,靈活地決定其是指向求大數(shù)或求小數(shù)的函數(shù) //便于方便靈活地調(diào)用各類函數(shù) 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 *); //將庫函數(shù)strcmp的地址賦值給函數(shù)指針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)調(diào)用strcmp,由cmp指向庫函數(shù)strcmp(),由a和b作調(diào)用strcmp()的參數(shù)。 //調(diào)用時,與聲明的情況類似,必須在*cmp周圍使用一對括號,使編譯程序正確操作, //同時這也是一種良好的編碼風(fēng)格,指示函數(shù)是通過指針調(diào)用的,而不是函數(shù)名。 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);
前者是返回值是指針的函數(shù);后者是一個指向函數(shù)的指針。
注意 本實驗用到的技巧也將會在JIT編譯器(如,瀏覽器中的JavaScript編譯器)中出現(xiàn),因為它們也是將數(shù)據(jù)轉(zhuǎn)換為代碼,然后再調(diào)用。請注意,試圖執(zhí)行代碼的攻擊可能會使用本實驗室的變體。這個實驗也說明了一個常見的錯誤,可能是不確定的。
Exercise 1:qsort中的函數(shù)指針
- 為什么 q s o r t ( ) qsort() qsort() 使用函數(shù)指針?
- q s o r t ( ) qsort() qsort() 是對任何類型的數(shù)組數(shù)據(jù)的通用排序例程(generic sorting routine ),因此,不同的數(shù)據(jù)類型需要不同的比較方法,這是由用戶提供的比較函數(shù) c o m p a r e ( ) compare() compare() 提供的。簡單說就是對數(shù)組進行排序。
- 聲明:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
- 參數(shù):
- base – 指向要排序的數(shù)組的第一個元素的指針。
- nitems – 由 base 指向的數(shù)組中元素的個數(shù)。
- size – 數(shù)組中每個元素的大小,以字節(jié)為單位。
- compar – 用來比較兩個元素的函數(shù)。如果 compar 返回值< 0,那么第一個參數(shù)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)
說明:當(dāng)s1<s2時,返回為負數(shù) 注意不是-1
當(dāng)s1==s2時,返回值= 0
當(dāng)s1>s2時,返回正數(shù) 注意不是1
即:兩個字符串自左向右逐個字符相比(按ASCII值大小相比較),直到出現(xiàn)不同的字符或遇'\0'為止。如:
“A”<“B” “a”>“A” “computer”>“compare”
特別注意:strcmp(const char *s1,const char * s2)這里面只能比較字符串,不能比較數(shù)字等其他形式的參數(shù)。
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() 函數(shù):
- 用途:
- 將一個普通文件映射到內(nèi)存中,通常在需要對文件進行頻繁讀寫時使用,這樣用內(nèi)存讀寫取代I/O讀寫,以獲得較高的性能;
- 將特殊文件進行匿名內(nèi)存映射,可以為關(guān)聯(lián)進程提供共享內(nèi)存空間;
- 為無關(guān)聯(lián)的進程提供共享內(nèi)存空間,一般也是將一個普通文件映射到內(nèi)存中。
- 頭文件:
#include <sys/mman.h>
- 原型:
void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offsize);
- 參數(shù)說明:
- start:指向欲映射的內(nèi)存起始地址,通常設(shè)為 NULL,代表讓系統(tǒng)自動選定地址,映射成功后返回該地址。
- length:代表將文件中多大的部分映射到內(nèi)存。
- prot:映射區(qū)域的保護方式??梢詾橐韵聨追N方式的組合:
- PROT_EXEC 映射區(qū)域可被執(zhí)行
- PROT_READ 映射區(qū)域可被讀取
- PROT_WRITE 映射區(qū)域可被寫入
- PROT_NONE 映射區(qū)域不能存取
- PROT_EXEC 映射區(qū)域可被執(zhí)行
- flags:影響映射區(qū)域的各種特性。在調(diào)用mmap()時必須要指定MAP_SHARED 或MAP_PRIVATE。
- MAP_FIXED:如果參數(shù)start所指的地址無法成功建立映射時,則放棄映射,不對地址做修正。通常不鼓勵用此旗標(biāo)。
- MAP_SHARED:對映射區(qū)域的寫入數(shù)據(jù)會復(fù)制回文件內(nèi),而且允許其他映射該文件的進程共享。
- MAP_PRIVATE:對映射區(qū)域的寫入操作會產(chǎn)生一個映射文件的復(fù)制,即私人的“寫入時復(fù)制”(copy on write)對此區(qū)域作的任何修改都不會寫回原來的文件內(nèi)容。
- MAP_ANONYMOUS:建立匿名映射。此時會忽略參數(shù)fd,不涉及文件,而且映射區(qū)域無法和其他進程共享。
- MAP_DENYWRITE:只允許對映射區(qū)域的寫入操作,其他對文件直接寫入的操作將會被拒絕。
- MAP_LOCKED:將映射區(qū)域鎖定住,這表示該區(qū)域不會被置換(swap)。
- 用途:
fd:要映射到內(nèi)存中的文件描述符。如果使用匿名內(nèi)存映射時,即flags中設(shè)置了MAP_ANONYMOUS,fd設(shè)為-1。
offset:文件映射的偏移量,通常設(shè)置為0,代表從文件最前方開始對應(yīng),offset必須是分頁大小的整數(shù)倍。
返回值:若映射成功則__返回映射區(qū)的內(nèi)存起始地址__,否則返回MAP_FAILED(-1),錯誤原因存于errno 中。
錯誤代碼:
EBADF 參數(shù)fd 不是有效的文件描述詞
EACCES 存取權(quán)限有誤。如果是M
P_PRIVATE 情況下文件必須可讀,使用MAP_SHARED則要有PROT_WRITE以及該文件要能寫入。
EINVAL 參數(shù)start、length 或offset有一個不合法。
EAGAIN 文件被鎖住,或是有太多內(nèi)存被鎖住。
ENOMEM 內(nèi)存不足。
- memcpy() 函數(shù):
- 用途:內(nèi)存復(fù)制。
- 原型:void *memcpy(void *dest, const void *src, size_t n);
- 功能:從src的開始位置拷貝n個字節(jié)的數(shù)據(jù)到dest。如果dest存在數(shù)據(jù),將會被覆蓋。
- 返回值:dest的指針。
- 頭文件:string.h
- index() 函數(shù):
- 用途:找地址。
- 原型:char * index(const char *s, int c);
- 功能:用來找出參數(shù)s 字符串中第一個出現(xiàn)的參數(shù)c 地址,然后將該字符出現(xiàn)的地址返回。字符串結(jié)束字符(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; }
- 要求:
- 加密方法只是讓數(shù)據(jù)XOR亦或整常數(shù)密碼(key,secret)。原始密碼已知,為255。
- 目標(biāo):從源代碼中移除密碼的依賴性,盡管有人知道源代碼也無法知道密碼(只考慮整數(shù))。
- 只修改LINE1和LINE2之間的代碼。
- 限制:
- 不能使用新函數(shù)或者新包。
- 需要在運行時,動態(tài)地創(chuàng)建f()所指向的新代碼。(f是隨輸入變化的)
- 新函數(shù)實現(xiàn)了與encrypt()相同的功能,除了常量來自輸入(讀入為secret)。
- 加密是密碼的異或,采用(a ^ c)形式,其中c是常數(shù)而不是c變量。
- 此外,盡管f()是在運行時動態(tài)創(chuàng)建的,但在使用時不應(yīng)該修改。在LINE3測試,即出現(xiàn)segfault。 hint:看看mprotect,它補充了mmap
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Qt中const?QString轉(zhuǎn)換?char?*可能的坑
本文主要介紹了Qt中const?QString轉(zhuǎn)換?char?*可能的坑,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07C++繼承中的對象構(gòu)造與析構(gòu)和賦值重載詳解
這篇文章主要為大家詳細介紹了C++繼承中的對象構(gòu)造與析構(gòu)和賦值重載,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03