c-将函数内部的一个区域分配给空指针,并保留外部的值



我有一个从套接字读取的函数,它返回一个存储数据包的char**,我的意图是使用一个NULLunsigned int指针来存储单个数据包的长度。

char** readPackets(int numToRead,unsigned int  **lens,int socket){
char** packets=(char**)malloc(numToRead);
int *len=(int*)malloc(sizeof(int)*numToRead);
*(lens)=len;
for(int i=0;i<numToRead;i++){
//read
packets[i]=(char*)malloc(MAX_ETH_LEN);
register int pack_len=read(socket,packets[i],MAX_ETH_LEN);
//TODO handler error in  case of freezing
if(pack_len<=0){
i--;
continue;
}
len[i]=pack_len;
}

return packets;
}

我是这样使用它的:

unsigned int *lens_out=NULL;
char **packets=readPackets(N_PACK,&lens,sniff_sock[handler]);

其中CCD_ 4是先前定义的常数。

现在的问题是,当我在函数内部时,一切都正常,事实上*(lens)指向len的同一内存区域,而在函数外部lens_out也指向同一区域。在函数内部len[i]等于*(lens[i])(我用gdb检查了它)。

问题是,在函数之外,即使lens_out指向相同区域,具有相同索引的len元素也是不同的,例如

len[0]=46
lens_out[0]=4026546640

有人能解释一下我在哪里犯的错吗?

您的语句char** packets=(char**)malloc(numToRead)肯定没有保留足够的内存。请注意,packets-数组的元素类型为char*,并且sizeof(char*)可能是8(最终为4),但非常不可能是1。所以你应该写

char** packets = malloc(sizeof(char*) * numToRead)

否则,您的写入超出了保留内存的范围,从而产生未定义的行为(可能是您解释的行为)。

请进一步注意,对于i--; continue;,由于您为第i个元素分配了新的内存块,因此会出现内存泄漏,但会丢失对之前保留的内存的引用。改为写入free(packets[i]);i--;continue;

此外,len[0]是一个积分类型,而lens[0]指的是指向int的指针。比较这两者没有意义。

首先,我想说的是,为了未来的维护,而不是为了认为是最佳(然而),应该写清楚的代码。这整个功能应该仅仅用read来代替。这是我帖子的关键。


现在的问题是,当我在函数内部时,一切都能正常工作

我不同意。在一个稍微宽泛的主题上,这里最大的问题是,您发布了一个问题,其中包含在未经修改地复制和粘贴时无法编译的代码,并且该问题与错误消息无关,因此我们无法在不猜测的情况下回答该问题。

我的猜测是您没有注意到这些错误消息;您正在运行一个过时的二进制,我们没有它的源代码,我们无法重现该问题,也看不到旧的源代码,所以我们无法帮助您。它和其他任何猜测一样有效。例如,还有另一个推测的答案:

您的语句char** packets=(char**)malloc(numToRead)肯定没有保留足够的内存。

malloc手册不能保证精确分配numToRead字节;事实上,对进程的分配往往在中执行,正如sleep手册不能保证毫秒/微秒的精确数量一样,它可能会分配更多,也可能会分配更少;在后一种情况下,malloc必须返回NULL您的代码需要检查

缓冲区溢出时,实现似乎正确是非常常见的。尽管如此,最好修复缓冲区溢出的问题。malloc不知道您正在分配的类型;你需要告诉它关于大小的一切,而不仅仅是元素的数量

附言:你可能希望selectsleep在你的循环中,你知道,"在冻结的情况下处理错误"或其他什么。通常,当您调用其中一个程序时,操作系统会将上下文切换到另一个程序,只有当有数据准备好处理时才会切换回来。通过在发送或接收后调用sleep,您可以提醒操作系统需要执行一些I/O。当你进行优化时,选择时机的能力可能是有益的。不过目前还没有。


函数内部len[i]等于*(lens[i])(我用gdb检查了它)。

我敢肯定你误解了。也许gdb隐含地取消了对指针的引用;这与C无关(所以不要把从gdb学到的任何东西与C相关的东西混淆)。

事实上,我强烈建议少学一点关于gdb的知识,多学一点assert的知识,因为前者不会帮助你从其他人那里记录你的代码,以供将来维护,包括我们,那些你向他们提问的人,后者会在哪里。如果您在代码中包含assert,那么几乎可以肯定,您的问题(和代码)比将N_PACK0包含在问题中要强大得多。

len[i]*(len[i])的类型不同,它们的值受类型解释方式的影响。这些值只有在转换为相同类型时才能被视为相等。我们可以通过C11/319p1看到这一点("值"的定义,其中标准确定它取决于类型)。len[i]int *值,其中*(len[i])int值。这两类值可能有不同的对齐方式、表示方式和。。。嗯,它们有完全不同的语义。一个用于积分数据,另一个是对对象或数组的引用。您不应该比较它们,无论它们看起来多么平等;你从这种比较中获得的信息实际上是无用的。

例如,不能在乘法表达式中使用len[i]。在这方面,他们肯定不平等。他们可能比较等于(作为引入隐式转换的比较的副作用),这对你来说是无用的信息,这是另一回事。

memcmp((int[]){0}, (unsigned char[]){ [sizeof int] = 42 }, sizeof int)可能返回0表示它们相等,但您知道字符数组包含一个额外的字节,对吗?耶。。。他们是平等的。。。


如果您正在使用malloc的返回值,则必须检查返回值(并且不要强制转换返回值),尽管我真的认为您应该重新考虑这方面的选项。

您使用malloc意味着每个使用您的函数的人都必须使用free;它将下游程序员锁定在一种反模式中,这种模式可能会撕裂软件的体系结构。您应该将分配逻辑用户界面逻辑的类别与

处理逻辑例如,您使用read,它使您有机会选择您喜欢的任何存储持续时间。这意味着您有大量的优化机会。它为下游程序员提供了编写灵活代码的机会,可以为所用内存分配您喜欢的任何存储时间。想象一下,另一方面,如果你必须释放每个函数的每个返回值。。。这就是你鼓励的混乱。

当涉及常量(即您的用例)时,这尤其是一个糟糕、低效的设计,因为您可以只使用一个自动数组,并完全取消对mallocfree的调用。。。您的下游程序员代码可能是:

char packet[size][count];
int n_read = read(fd, packet, size * count);

也许您认为使用malloc为数据包分配(稍后读取)n空间比使用其他方法分配n空间更快。你应该测试这个理论,因为根据我的经验,计算机往往会被优化为更简单、更短、更简洁的逻辑。

预期中:

但我不能那样return packet;

正确。您不能将return packet;发送给下游程序员,因此您可以修改由参数指向的对象。但这并不意味着您应该使用malloc

不幸的是,太多的程序采用了这种"到处使用malloc"的心态。这让人想起我们被灌输的"不要使用goto"的废话。与其听货运邪教的宣传,我建议你批判性地思考你听到的,因为你的同龄人和你处于同样的地位;他们不一定知道自己在说什么。

最新更新