6

In C, a typedef doesn't grant you any additional type safety. You can use the the new type any place you can use the old type. Sometimes that's what I want, and sometimes it is not. Sometimes I want the compiler to warn me if I misuse my new type.

To make that happen, I sometimes do something like this:

typedef struct {
    int value;
} NewType;

NewType doSomethingNT(NewType a, NewType b) {
    return a.value + b.value;
}

Compared to:

int doSomethingI(int a, int b) {
    return a + b;
}

(That plus is just an example. Let's assume that either there's a function call overhead in both cases, or else I ask the function to be inlined in both cases. But let's not compare doSomethingNT to the bare + operator, obviously the latter is faster because it doesn't have the function call overhead)

I guess I'm asking, is there any overhead in "boxing" a primitive type a one element struct, but using that struct as a value type. (I.e. I'm not calling malloc and using pointers to it like how boxing works in Java.)

Tim
  • 4,999
  • 3
  • 24
  • 29
  • 1
    The only overhead is compilation time – deviantfan Mar 06 '14 at 19:10
  • Veeeerrrry similar to [c-class-wrapper-around-fundamental-types](http://stackoverflow.com/questions/17793298/c-class-wrapper-around-fundamental-types), but that's about C++... – Mooing Duck Mar 06 '14 at 19:12
  • 2
    This is interesting but probably the best answer is to measure some actual programs, version 1 using plain types, version 2 using your "box types". Probably the optimizer can make version 2 no slower than version 1 – Brandin Mar 06 '14 at 19:25
  • 2
    Measuring such a small change can be difficult since if there is overhead, it's likely to be quite minimal. The best way to answer this question, I think, is to compile each version to assembly and see what the difference is (if any). – mah Mar 06 '14 at 19:28

1 Answers1

4

I tried this with the clang (specifically Apple LLVM version 5.0 (clang-500.2.79) (based on LLVM 3.3svn)) at -O2.

The source:

#include <stdio.h>

struct da_int {
    short i;
};

struct da_int add(struct da_int x, struct da_int y) {
    struct da_int rv = {x.i + y.i};
    return rv;
}

int main(int argc, char *argv[]) {
    struct da_int x = {5}, y = {3};
    printf("%d\n", add(x, y).i);
}

The compiler foiled my plan slightly—the generated main never calls add, instead computing the result at compile-time:

_main:                                  ## @main
    .cfi_startproc
## BB#0:
    pushq    %rbp
Ltmp7:
    .cfi_def_cfa_offset 16
Ltmp8:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp9:
    .cfi_def_cfa_register %rbp
    leaq    L_.str(%rip), %rdi
    movl    $8, %esi
    xorb    %al, %al
    callq    _printf
    xorl    %eax, %eax
    popq    %rbp
    ret
    .cfi_endproc

It did generate an add function though! It takes its arguments in registers and returns a result in a register. The wrapper struct disappears completely in the compiler output, at least for this trivial case:

_add:                                   ## @add
    .cfi_startproc
## BB#0:
    pushq    %rbp
Ltmp2:
    .cfi_def_cfa_offset 16
Ltmp3:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp4:
    .cfi_def_cfa_register %rbp
    addl    %esi, %edi
    movw    %di, %ax
    popq    %rbp
    ret
    .cfi_endproc

Given that, I wouldn't worry too much about extra overhead from doing this.

Left For Archive
  • 2,626
  • 1
  • 18
  • 19