Lance Pollard
Lance Pollard

Reputation: 79208

How to properly write a function that adds two integers in C

I am new to C and getting a feel for what is most optimized and what is the correct way to deal with pointers, values, references, etc..

I have started by creating a simple integer add function.

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);
}

From my understanding doing add(a, b) will copy the values into the function, which means it's slower performance-wise than passing in pointers. So I try to create two add functions, renaming this one to 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);
}

I'm wondering a few things:

  1. If there is a way to have the behavior of both these functions in one function. Or if not (or if performance isn't optimal in that case), how typically to handle both cases, if it's just 2 separate functions with naming convention or something, etc.
  2. Which one is the most optimized form. From my understanding it is add_pointers because nothing is copied. But then you can't do a simple add(1, 1), so that's no fun from an API standpoint.

Upvotes: 2

Views: 2324

Answers (3)

Luis Colorado
Luis Colorado

Reputation: 12670

From my point of view, depending on if you have a compiler capable of making inline function expanding, the quickest way to do it to simply:

inline int add(int a, int b)
{
    return a + b;
}

because the compiler probably will mostly avoid the subroutine call/return at all and will use the best expansion available in each use case (in most cases, this will be inlined as a single add r3, r8 instruction.) This can take well less than a single clock cycle in many today multicore and pipelined cpus.

In case you don't have access to such a compiler, then probably the best way to simulate that scenario is to:

#define add(a,b) ((a) + (b))

and you'll be inlining, while maintaining the function notation. But the premises fail with this response, as you asked for a function, depending on the priority of the premises :)

When you think on the best way to do a function call, first think that, for small functions, the heaviest task you make is to do a subroutine call at all... as it takes time to push the return address, think that in this case, the worst part is to have to call a function, just to add it's two parameters (adding two values stored in registers requires only one instruction, but with a function call, it requires at least three ---the call, the add, and the return back) The add doesn't require much time if the addends are already in registers, but a subroutine call normally requires to push a register in the stack and to pop it later. Those are two memory accesses, that will cost more, even if cached in the instruction cache.

Of course, if the compiler knows that the function is cacheable, and you use it several times with the same parameters in one expression of in the same block, it can cache the result value to be used later, and save the cost of making the sum again. The thing becomes as if the best way of proceeding was to see which exact scenario we are dealing with. But at this point, the major cost of adding two numbers, is by far, the cost of jailing it in a function envelope.

EDIT

I tried the following example, and compiled it in an arm7 architecture (raspberry pi B+, freebsd, clang compiler) and the result was far than good :)

inline.c

inline int sum(int a, int b)
{
    return a + b;
}

int main()
{
    int a = 3, b = 2, c = sum(a, b);
}

resulting into:

    /* ... */
    .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

As you see, the only code for main consisted in storing 0 return value in r0 (exit code) ;)

Just in case i compiled add as an external library function:

int add(int a, int b);

int main()
{
    int a = 3, b = 2, c = sum(a, b);
}

would result in:

    .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

You can see that the call to the function will be made, anyway, as the compiler has not been informed of the kind of function it has at hand (even if the result code is not going to be used, as the function can have lateral effects), and has to be called, anyway.

By the way, and as mentioned, the way of passing references to a function involves dereferencing those pointers, and that means normally memory accesses (which is far more expensive than adding two registers together)

Upvotes: 1

Lundin
Lundin

Reputation: 213513

which means it's slower performance-wise than passing in pointers

That's where you got it wrong. On a typical 32 bit computer, int are 32 bit and pointers are 32 bit. So the actual amount of data passing is identical between both versions. However, use of pointers can boil down to indirect access machine code, so it might actually yield less inefficient code in some circumstances. In the general case int add(int a, int b) is probably the most efficient.

As a rule of thumb, it is fine to pass all standard integer and float types by value to functions. But structs or unions should be passed through pointers.

In this particular case the compiler is likely to "inline" the whole function, replacing it with a single addition instruction in the machine code. After which the whole parameter passing turns into a non-issue.

Overall, don't ponder performance too much as a beginner, it is an advanced topic and depends on the specific system. Instead, focus on writing as readable code as possible.

Upvotes: 1

dbush
dbush

Reputation: 223699

Anytime you call a function with parameters you're copying the values of the parameters. In your examples it's just a matter of whether you're copying pointer values or integer values. Copying an int will not be noticeably faster or slower than copying a pointer, but with a pointer you have an additional read from memory whenever you dereference the pointer.

For any simple data type, you're better of just accepting parameters by value. The only time it makes more sense to pass a pointer is if you're dealing with an array or struct which can be arbitrarily large.

Upvotes: 1

Related Questions