关于输入参数的顺序

时间:2014-01-02 10:39:03

标签: c++ c performance code-readability input-parameters

对于函数/方法包含许多输入参数,如果以不同的顺序传入它会有所不同吗?如果是,在哪些方面(可读性,效率......)?我对自己的功能/方法应该怎么做更好奇呢?

在我看来:

  1. 通过引用/指针传递的参数通常在参数传递值之前。例如:

    void* memset( void* dest, int ch, std::size_t count ); 
    
  2. 目标参数通常出现在源参数之前。例如:

    void* memcpy( void* dest, const void* src, std::size_t count );
    
  3. 除了一些硬约束之外,即具有默认值的参数必须是最后的。例如:

    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
  4. 无论传递的顺序如何,它们都是功能相同的(达到相同的目标)。

4 个答案:

答案 0 :(得分:12)

有几个原因可以解决 - 见下文。 C ++标准本身并没有强制要求在这个空间中任何特定的行为,因此没有可移植的方式来推断性能影响,即使某个可执行文件中的某些东西显然(稍微)更快,更改程序中的任何位置,或编译器选项或版本,可能会删除甚至扭转早期的好处。在实践中,听到人们谈论参数排序在性能调整中具有任何重要性是非常罕见的。如果您真的在乎,最好检查一下您自己的编译器输出和/或基准测试结果代码。

例外

传递给函数参数的表达式的求值顺序是未指定的,并且它很可能会受到它们在源代码中出现的顺序的更改的影响,其中一些组合在CPU执行中工作得更好管道,或早先提出异常,使其他参数准备短路。如果某些参数是临时对象(例如表达式的结果),这对于分配/构造和销毁/解除分配来说是昂贵的,则这可能是重要的性能因素。同样,对程序的任何更改都可以删除或撤消之前观察到的利益或惩罚,因此如果您关心这一点,您应该在进行函数调用之前为要先评估的参数创建一个命名临时值。

寄存器与缓存(堆栈内存)

某些参数可以在寄存器中传递,而其他参数则被推送到堆栈 - 这实际上意味着至少输入最快的CPU缓存,并暗示它们的处理可能会更慢。

如果函数最终无法访问所有参数,并且选择是将参数X放在寄存器中而将Y放在堆栈中,反之亦然,那么它们的传递方式并不重要,但鉴于该函数可能具有影响实际使用哪些变量的条件(如果语句,开关,可能输入或未输入的循环,提前返回或中断等),如果变量“变量”可能会更快。实际上不需要的是在堆栈上,而需要的是在寄存器中。

有关调用约定的一些背景和信息,请参阅http://en.wikipedia.org/wiki/X86_calling_conventions

对齐和填充

理论上,性能可能会受到参数传递约定的影响:参数可能需要特定的对齐 - 或者只是全速 - 访问堆栈,编译器可能会选择填充而不是重新排序值推动 - 除非参数数据符合缓存页面大小的规模,否则很难想象它是重要的

非绩效因素

你提到的其他一些因素可能非常重要 - 例如,我倾向于先放置任何非常量指针和引用,并将函数命名为load_xxx,因此我对可以修改哪些参数有一致的期望。通过它们的顺序。但是,没有特别重要的惯例。

答案 1 :(得分:3)

严格来说无关紧要 - 参数被压入堆栈,并且函数通过以某种方式从堆栈中获取它们来访问它们。

但是,大多数C / C ++编译器允许您指定替代调用约定。例如,Visual C ++支持__fastcall约定,该约定将前两个参数存储在ECX和EDX寄存器中,(理论上)应该在适当的情况下提供性能改进。

还有__thiscallthis指针存储在ECX寄存器中。如果您正在使用C ++,那么这可能很有用。

答案 2 :(得分:3)

这里有一些答案提到呼叫惯例。它们与您的问题无关:无论您使用何种调用约定,您声明参数的顺序都无关紧要。只要寄存器传递相同数量的参数并且堆栈传递相同数量的参数,寄存器传递哪些参数以及哪些参数由堆栈传递都无关紧要。请注意,大小超过本机体系结构大小的参数(32位为4个字节,64位为8个字节)由一个地址传递,因此它们以与较小大小数据相同的速度传递

我们举一个例子:

您有一个包含6个参数的功能。你有一个调用约定,让我们称之为CA,通过寄存器传递一个参数,其余的(在本例中为5)通过​​堆栈,第二个调用约定,让我们称之为CB,通过寄存器传递4个参数,其余(在这种情况下为2)通过堆栈。

现在,当然CA会比CB快,但它与声明参数的顺序无关。对于CA来说,无论您首先声明哪个参数(通过寄存器)以及您声明第2个,第3个第6个(堆栈),以及对于CB,无论您为寄存器声明哪4个参数,它都会一样快。并声明为最后2个参数。


现在,关于你的问题:

唯一必须的规则是必须最后声明可选参数。没有非可选参数可以跟随可选参数。

除此之外,您可以使用您想要的任何订单,我能给您的唯一强烈建议是保持一致。选择一个模型并坚持下去。

您可以考虑的一些指导原则:

  • 目的地来源于源头。这是接近destination = source
  • 缓冲区的大小位于缓冲区之后:f(char * s, unsigned size)
  • 首先输入参数,最后输出参数(这与我给你的第一个冲突)

但是参数的顺序没有“错误”或“正确”,甚至没有普遍接受的指导方针。选择一些东西并保持一致。

修改

我想到了一种“错误”的方式来命令你参数:按字母顺序:)。

修改2

  

例如,对于CA,如果我传递向量(100)和int,如果vector(100)首先出现,即使用寄存器加载更大的数据类型,则会更好。正确?

没有。正如我所提到的,数据大小无关紧要。让我们谈谈32位架构(同样的讨论适用于任何架构16位,64位等)。让我们分析一下我们可以得到的关于与体系结构的原生大小相关的参数大小的3个案例。

  • 相同大小:4字节参数。这里没什么可说的。
  • 较小的大小:将使用4字节的寄存器或在堆栈上分配4字节。所以这里也没有什么有趣的。
  • 较大的大小:(例如,包含许多字段的结构或静态数组)。无论选择哪种方法传递此参数,此数据都驻留在内存中,传递的是指向该数据的指针(大小为4字节)。我们再次在堆栈上有一个4字节的寄存器或4字节。

参数的大小无关紧要。

编辑3

@TonyD如何解释,如果您不访问所有参数,顺序就很重要。看他的答案。

答案 3 :(得分:1)

我以某种方式找到了一些相关页面。

https://softwareengineering.stackexchange.com/questions/101346/what-is-best-practice-on-ordering-parameters-in-a-function

https://google.github.io/styleguide/cppguide.html#Function_Parameter_Ordering

因此,首先谷歌的C ++风格并没有真正回答这个问题,因为它无法回答输入参数或输出参数中的实际顺序。

另一页基本上表明订单参数在某种意义上易于理解和使用。

为了便于阅读,我个人更喜欢根据字母顺序订购参数。但是你也可以采用一些策略来命名参数,使它们很容易被理解和使用。