我是C语言的新手,可以了解最优化的内容以及处理指针,值,引用等的正确方法。
我首先创建了一个简单的整数add
函数。
int
add(int a, int b) {
return a + b;
}
void
main() {
// these work
int sum = add(1, 1);
int a = 1;
int b = 1;
int c = add(a, b);
// this doesn't
int d = add(&a, &b);
int e = add(*a, *b);
}
据我了解,add(a, b)
会将值复制到函数中,这意味着在性能上比传递指针要慢。因此,我尝试创建两个添加函数,将此函数重命名为add_values
。
int
add_pointers(int *a, int *b) {
return (*a) + (*b);
}
int
add_values(int a, int b) {
return a + b;
}
void
main() {
// these work
int sum = add_values(1, 1);
int a = 1;
int b = 1;
int c = add_values(a, b);
// this works now
int d = add_pointers(&a, &b);
// not sure
int e = add(*a, *b);
}
我想知道几件事:
add_pointers
,因为没有任何内容被复制。但是,然后您就无法执行简单的add(1, 1)
,因此从API的角度来看这没什么好玩的。答案 0 :(得分:1)
每次调用带有参数的函数时,您都将复制参数的值。在您的示例中,只需要复制指针值还是整数值即可。复制int
不会比复制指针快或慢,但是使用指针时,只要取消引用指针,您就可以从内存中另外读取一次。
对于任何简单的数据类型,最好只按值接受参数。唯一有意义的是传递指针是在处理数组或struct
时,它可以任意大。
答案 1 :(得分:1)
这意味着在性能方面比传递指针要慢
那是你弄错了的地方。在典型的32位计算机上,int
是32位,指针是32位。因此,两个版本之间的实际数据传递量是相同的。但是,使用指针可以归结为间接访问机器代码,因此在某些情况下,它实际上可能会产生效率较低的代码。通常,int add(int a, int b)
可能是最有效的。
根据经验,可以将所有标准整数和浮点类型按值传递给函数。但是结构或联合应该通过指针传递。
在这种特殊情况下,编译器可能会“内联”整个函数,用机器代码中的单个加法指令替换它。之后,整个传递的参数将变为非问题。
总体而言,对于初学者来说,不要过多地考虑性能,这是一个高级主题,并取决于特定的系统。取而代之的是,重点放在编写尽可能可读的代码上。
答案 2 :(得分:1)
从我的角度来看,取决于您是否有能够使inline
函数扩展的编译器,最简单的方法是:
inline int add(int a, int b)
{
return a + b;
}
因为编译器可能会基本上完全避免子例程的调用/返回,并且将在每种使用情况下使用最佳扩展(在大多数情况下,这将作为单个add r3, r8
指令内联。)在当今许多多核和流水线CPU中,占用的时间远远少于单个时钟周期。
如果您无权使用此类编译器,则模拟该方案的最佳方法可能是:
#define add(a,b) ((a) + (b))
,您将在保持函数符号的同时进行内联。但是,前提是您要求功能,前提是前提会失败,前提是前提是前提:)
当您想到进行函数调用的最佳方法时,首先请考虑一下,对于小型函数,您要做的最重的任务就是根本就进行子例程调用...因为要花一些时间来推回返回地址,认为在这种情况下,最糟糕的部分是必须调用一个函数,只是要添加它的两个参数(将两个存储在寄存器中的值相加仅需要一条指令,而对于函数调用,则至少需要三个-如果加数已经在寄存器中,则添加不需要很多时间,但是子例程调用通常需要将寄存器压入堆栈并稍后弹出。这是两次内存访问,即使缓存在指令缓存中,也会花费更多。
当然,如果编译器知道该函数是可缓存的,并且在同一块的一个表达式中以相同的参数多次使用它,则可以将结果值缓存为稍后使用,并节省再次进行总和的费用。事情似乎就好像最好的处理方法是查看我们正在处理的确切方案。但是,到目前为止,将两个数相加的主要成本是将其放入函数信封的成本。
我尝试了以下示例,并将其编译为arm7架构(raspberry pi B +,freebsd,clang编译器),结果远远不如:)
inline int sum(int a, int b)
{
return a + b;
}
int main()
{
int a = 3, b = 2, c = sum(a, b);
}
导致:
/* ... */
.file "inline.c"
.globl main @ -- Begin function main
.p2align 2
.type main,%function
.code 32 @ @main
main:
.fnstart
@ %bb.0:
mov r0, #0
bx lr
.Lfunc_end0:
.size main, .Lfunc_end0-main
如您所见,main的唯一代码在于将0
的返回值存储在r0
中(退出代码);)
以防万一我将add
编译为外部库函数:
int add(int a, int b);
int main()
{
int a = 3, b = 2, c = sum(a, b);
}
将导致:
.file "inline.c"
.globl main @ -- Begin function main
.p2align 2
.type main,%function
.code 32 @ @main
main:
.fnstart
@ %bb.0:
.save {r11, lr}
push {r11, lr}
.setfp r11, sp
mov r11, sp
mov r0, #3
mov r1, #2
bl sum <--- the call to the function.
mov r0, #0
pop {r11, pc}
.Lfunc_end0:
.size main, .Lfunc_end0-main
.cantunwind
.fnend
无论如何,您都会看到对函数的调用,因为没有通知编译器手头有什么类型的函数(即使不会使用结果代码作为函数)会产生副作用),无论如何都必须调用它。
顺便提一句,如前所述,将引用传递给函数的方式涉及取消引用那些指针,这通常意味着内存访问(这比将两个寄存器加在一起要昂贵得多)