double free 简介:
Double free errors occur when free() is called more than once with the same memory address as an argument. -owasp
从字面意思,double free 就是对同一地址,释放了超过一次,而这并不一定意味着我们在代码中写了两次 free(),极有可能是某些库方法自身使用了 free();
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
void gen(int pos, int n, int open, int close, int *returnSize, char **r, char *str) {
if (close == n) {
(*returnSize)++;
r = (char **)realloc(r, (*returnSize + 1) * sizeof(char *)); //(1)
str[2 * n] = '\0';
r[*returnSize - 1] = (char *)malloc((2 * n + 1) * sizeof(char));
r[*returnSize - 1] = strcpy(r[*returnSize - 1], str);
return;
} else {
if (open > close) {
str[pos] = ')';
gen(pos + 1, n, open, close + 1, returnSize, r, str); //(2)
}
if (open < n) {
str[pos] = '(';
gen(pos + 1, n, open + 1, close, returnSize, r, str); //(3)
}
}
}
char **generateParenthesis(int n, int *returnSize) {
*returnSize = 0;
if (n <= 0) {
return NULL;
}
char *str = (char *)malloc(2 * n * sizeof(char) + 1);
char **result = (char **)malloc(sizeof(char *));
gen(0, n, 0, 0, returnSize, result, str);
free(str);
return result;
}
int main(void) {
int num = 3;
int size = 0;
char **r = generateParenthesis(num, &size);
for (size_t i = 0; i < size; ++i) {
printf("%zu : %s\n", i, r[i]);
}
return 0;
}
原因
上述代码在运行时会报 double free,只有在 return 前写了 free(str),这里不可能会重复调用,于是只有realloc()可能导致这个错误。在realloc过程中,如果是扩展内存区域,会返回新指针,并释放原始指针。
观察 (1)和(2),在一次递归中,传入的r为同一地址,在(1)中,由于执行了realloc,所以会被释放,返回新的地址。
而当(1)执行完毕,(2)中仍然是之前的地址,再次执行realloc,便会遇到 double free 问题。原因找到了,那么应该怎么解决呢。
修复
当使用递归时,尤其是树递归,若要重复访问某一地址,则应该避免此地址收到更改。这时我们便可以献上三重指针大法(之前变量为二重指针),代码可以修改为:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
void gen(int pos, int n, int open, int close, int *returnSize, char ***r, char *str){
if (close == n){
(*returnSize) ++;
*r = (char **)realloc(*r, (*returnSize + 1) * sizeof(char *));
str[2*n] = '\0';
( *r)[*returnSize - 1] = (char *)malloc((2*n + 1)*sizeof(char));
( *r)[*returnSize - 1] = strcpy((*r)[*returnSize - 1] , str);
return;
}else{
if (open > close){
str[pos] = ')';
gen(pos+1, n, open, close + 1 ,returnSize, r, str);
}
if (open < n){
str[pos] = '(';
gen(pos+1, n, open+1, close, returnSize, r, str);
}
}
}
char ** generateParenthesis(int n, int* returnSize){
*returnSize = 0;
if (n <= 0){
return NULL;
}
char * str = (char *)malloc(2*n*sizeof(char)+1);
char ** result = (char **)malloc(sizeof(char *));
gen(0, n, 0, 0, returnSize, &result, str);
free(str);
return result;
}
int main(void) {
int num = 3;
int size = 0;
char ** r = generateParenthesis(num, &size);
for (size_t i =0; i<size; ++i){
printf("%zu : %s\n", i, r[i]);
}
return 0;
}
观察,我们将 r = (char **)realloc(r, (*returnSize + 1) * sizeof(char *)); 改为 *r = (char **)realloc(*r, (*returnSize + 1) * sizeof(char *));,并在传参时,为char ***,虽然*r的值仍然会因为realloc而被多次修改,但是每次递归我们都传入同样的r,便保证了引用一致的同时不会触发 double free 问题。