Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Preference between memcpy and dereference

When copying a known struct in memory, would you prefer using memcpy or dereference? why? Specifically, in the following code:

#include <stdio.h>
#include <string.h>

typedef struct {
    int foo;
    int bar;
} compound;

void copy_using_memcpy(compound *pto, compound *pfrom)
{
    memcpy(pto, pfrom, sizeof(compound));
}
void copy_using_deref(compound *pto, compound *pfrom)
{
    *pto = *pfrom;
}

int main(int argc, const char *argv[])
{
    compound a = { 1, 2 };
    compound b = { 0 };
    compound *pa = &a;
    compound *pb = &b;

    // method 1
    copy_using_memcpy(pb, pa);
    // method 2
    copy_using_deref(pb, pa);
    printf("%d %d\n", b.foo, b.bar);

    return 0;
}

Would you prefer method 1 or method 2? I looked at the assembly generated by gcc, and it seems that method 2 uses less instructions than method 1. Does it imply that method 2 is preferable in this case? Thank you.

like image 966
Shao-Chuan Wang Avatar asked Sep 11 '12 06:09

Shao-Chuan Wang


2 Answers

I can't think of any good reason to use memcpy() rather than an assignment when copying a struct (as long as you don't need to do a deep copy or something involving the struct hack or a flexible array member, none of which apply in this case).

They have exactly the same semantics, and the assignment (a) likely gives the compiler more opportunities for optimization, and (b) has less risk of getting the size wrong.

Some very old C compilers probably didn't support struct assignment, but that's no longer a significant concern.

(There are additional reasons to prefer assignment in C++, but your question is about C.)

Incidentally, the parentheses in

(*pto) = (*pfrom);

are unnecessary; the unary * binds tightly enough that this:

*pto = *pfrom;

is both correct and sufficiently clear to most readers.

like image 163
Keith Thompson Avatar answered Nov 09 '22 01:11

Keith Thompson


I tried to run this with Google's benchmark:

#include <benchmark/benchmark.h>
#include <stdio.h>
#include <string.h>

typedef struct {
    int foo;
    int bar;
    int a;
    int b;
    int c;
    int d;
    int e;
    int f;
    int g;
} compound;

static void copy_using_memcpy(benchmark::State& state) {
    compound a = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    compound b = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    compound* pa = &a;
    compound* pb = &b;
    for (auto _ : state) memcpy(pa, pb, sizeof(compound));
}
static void copy_using_deref(benchmark::State& state) {
    compound a = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    compound b = {0, 0, 0, 0, 0, 0, 0, 0, 0};
    compound* pa = &a;
    compound* pb = &b;
    for (auto _ : state) *pa = *pb;
}

BENCHMARK(copy_using_memcpy);
BENCHMARK(copy_using_deref);

BENCHMARK_MAIN();

The result is like:

> g++ benchmark.cc -lbenchmark -lpthread && ./a.out
2020-11-20T20:12:12+08:00
Running ./a.out
Run on (16 X 1796.56 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x8)
  L1 Instruction 32 KiB (x8)
  L2 Unified 512 KiB (x8)
  L3 Unified 4096 KiB (x1)
Load Average: 0.29, 0.15, 0.10
------------------------------------------------------------
Benchmark                  Time             CPU   Iterations
------------------------------------------------------------
copy_using_memcpy       2.44 ns         2.44 ns    282774223
copy_using_deref        1.77 ns         1.77 ns    389126375

In the original example, with only two fields, the time is roughly the same.

like image 21
Sean Avatar answered Nov 09 '22 01:11

Sean