为什么许多C函数使用指针来传递数据,而不是使用;return";?
这或多或少是一个关于方法论和基本原理的问题。在为Linux编写各种内核模块时,我被我认为是一种笨拙的设计函数的方式弄糊涂了。例如,要检索给定路径的文件的inode,我必须使用以下命令:为什么许多C函数使用指针来传递数据,而不是使用;return";?,c,function,C,Function,这或多或少是一个关于方法论和基本原理的问题。在为Linux编写各种内核模块时,我被我认为是一种笨拙的设计函数的方式弄糊涂了。例如,要检索给定路径的文件的inode,我必须使用以下命令: struct inode *inode; struct path path; kern_path(path_name, LOOKUP_FOLLOW, &path); inode = path.dentry->d_inode; 为什么不仅仅是一个像这样工作的函数: struct inode inod
struct inode *inode;
struct path path;
kern_path(path_name, LOOKUP_FOLLOW, &path);
inode = path.dentry->d_inode;
为什么不仅仅是一个像这样工作的函数:
struct inode inode;
struct path path = kern_path(path_name, LOOKUP_FOLLOW);
inode = path.dentry->d_inode;
看起来更直观。这有利于减少来回复制内存块
使用此方法允许调用的函数仅修改传入结构的某些部分,而不复制任何内存。传入指向用于返回数据的结构的指针有助于传回大量数据,而无需实际传递。你只是来回传递一个指针,而不是一个巨大的结构 您的示例只显示了一个填充的元素。有些驱动程序会传回包含几十个(如果不是超过一百个)字段的结构。这将获得非常昂贵的内存和cpu。您希望从内核中挤出尽可能多的内容 此方法允许您做的另一件事是让内存管理发生在函数之外,使函数更通用和可重用 更不用说,如果你想返回不止一件东西,这几乎是唯一的方法
另一个好处是,它允许您非常清楚地定义与驱动程序的接口。声明一个结构,这就是你的API。没有其他东西可以进出。你会用它做什么 函数必须能够返回某种错误代码,这样用户才能确保函数成功。有两个明显的选择:
由于在C中只能返回1个值,因此必须将某个值作为参数,因为最终需要向用户返回两个值(错误代码和值)。内核是一个具有特殊约束的C程序。特别是,不允许调用堆栈很深(IIRC,限制为4KB) 当您返回一个
struct
时,ABI(请参阅…)命令(除了一些简短的struct
用两个词进行拟合)返回的struct
通过堆栈。因此,这种样式将有利于相当大的堆栈帧,因此更容易满足堆栈限制
顺便说一句,通常的风格是返回一些整数错误代码,并修改参数指针指向的数据
在x86-64/Linux上,返回两个标量值(整数、指针、枚举、双精度等)的结构绝对值得,请参阅。实际原因比其他答案简单得多:这只是习惯,来自一个不再存在的约束。所有其他的理由都是事后捏造出来的;对于相反的事物,通常有一个相等和相反的理由
在旧的C编译器中,所有参数和返回值必须能够装入寄存器。这意味着,如果它们较大,则必须通过地址传递。好处是,当传递地址时,函数可以对该地址的数据进行操作,并且调用方可以使用更改。当传递不可能的副本时。返回的结构通常通过指针传递,除非它们足够小,可以装入寄存器。除非您的编译器真的很笨,否则不会涉及大内存拷贝。@o11c这完全不相关。即使“智能”编译器可以优化返回值,调用代码也可能需要将结果存储在局部变量中,然后进行复制。而且,很多相关代码都是在编译器明显不够先进的时候编写的,并没有人会因为编译器的改进而改变整个编码模式。最后但并非最不重要的一点是,您的整个假设依赖于完整的源代码,但这不适用于库,这里的情况正是如此(至少对于某些函数)(也请参见Basile的答案-相同的理由系列)…因为许多C函数都是由优秀的C程序员编写的…因为只有很少的函数返回简单数据,异常信息几乎是普遍首选的返回数据。然而,共享内存中的引用信息可能非常庞大,如果引用是样板文件,则通常会被卷积到一个定义的宏中,如
somestatus=kern\u path(kern\u path\u PARMS)
。至少在x86-64上,一段时间内限制为16KB。过去几年它一直是8KB,但这还不够(文件系统代码,主要是xfs,很容易溢出,特别是在i/o堆栈深处触发内存回收时)。结构通常在堆栈上。@o11c:但返回结构就是生成它的静默副本,因此您可以(暂时)得到它堆栈上有它的两个副本。@BasileStarynkevitch在任何合适的编译器上都没有。您可能需要担心的唯一一件事是它是否无法解决别名问题。有些人认为,返回值并在参数中传递错误可以更容易地确保您不会忽略错误。当然,有\uuuu属性((warn\u unused\u result))
,但人们通常太懒了,不想始终如一地使用它。@o11c:Wow,不知道warn\u unused\u result。我已经习惯了围棋中类似的功能。当然,在go中,这是一个错误而不是警告,并且您不能关闭它(这不是可选的)。