最近我了解到GCC的新-fanalyzer特性,并决定在我们的代码库中试用它。结果很有趣,但是有一个函数我怀疑是假阳性。
这是一个有争议的函数,GCC报告了一个malloc泄漏(请看这里):
char** va_to_argv(va_list args, int32_t* argc)
{
va_list a;
char* arg;
int32_t n;
int32_t l;
int32_t sz = 0;
int32_t cnt = 0;
va_copy(a, args);
while ((arg = va_arg(a, char*)) != NULL)
{
sz += strlen(arg) + 1;
++cnt;
}
va_end(a);
struct s
{
char* argv[cnt + 1];
char data[sz];
};
struct s* tmp = calloc(1, sizeof(*tmp));
for (n=0, l=0; n<cnt; ++n)
{
tmp->argv[n] = &tmp->data[l];
strcpy(tmp->argv[n], va_arg(args, char*));
l += strlen(tmp->argv[n]) + 1;
}
tmp->argv[cnt++] = NULL;
if (argc)
{
*argc = cnt;
}
return &(tmp->argv[0]);
}它应该将va_list (仅包含char*,并以NULL作为最后一个参数)转换为等效的argc/argv表示形式。据我所知,从来没有任何问题涉及到这个特定的功能,所以当GCC报告tmp在返回声明中被泄露时,我感到非常惊讶。
是的,calloc()的结果存储在tmp中,函数不直接返回tmp,因此乍一看,警告是有意义的。但是,返回的值是指向tmp的第一个元素的指针,结构的第一个元素的地址与结构本身的地址相同(因此free(&(tmp->argv[0]))应该是合法的)。
ISO/IEC 9899,第6.7.2.1节 指向结构对象的指针,经过适当的转换,指向它的初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。在structure对象中可能有未命名的填充,但在开始时没有。
不幸的是,这段代码依赖于特定于GCC的扩展(在struct中是VLA),而clang不支持这个扩展。否则,我会想要反复检查克郎的静态分析器对此有什么看法。
编辑
即使类型不匹配(正如Lundin指出的那样),警告仍然存在,即使在函数的这个版本中(当然,它不再做任何有意义的事情):
char** va_to_argv(va_list args, int32_t* argc)
{
struct s
{
char** argv;
char data[10];
};
struct s* tmp = calloc(1, sizeof(*tmp));
if (tmp == NULL) return NULL;
return tmp->argv;
}发布于 2020-06-15 09:31:01
没有VLA的简化版本:
char **va_to_argv(va_list args, int32_t *argc)
{
va_list a;
char *arg;
char *data;
char **arr;
int32_t n;
int32_t ii;
int32_t sz = 0;
int32_t cnt = 0;
va_copy(a, args);
for(cnt=0; (arg = va_arg(a, char*)) ; cnt++ )
{
sz += 1+ strlen(arg) ;
}
va_end(a);
data = malloc(sz+1);
arr = calloc(cnt+1, sizeof *arr);
for (n=ii=0; n<cnt; n++)
{
arr[n] = &data[ii];
strcpy(arr[n], va_arg(args, char*));
ii += 1 + strlen(arr[n]) ;
}
arr[n++] = NULL;
if (argc) *argc = n;
return arr;
}编辑:如果只想分配一个对象,可以在*char[]数组之后定位char[]数组,然后返回指针数组:
arr = malloc(sz+1 + (cnt+1) * sizeof *arr);
data = (char*) ( &arr[cnt+1] ); // Put data after the last arr[] elementhttps://stackoverflow.com/questions/62384605
复制相似问题