3

Lets say I have two structs:

typedef struct {
    uint64_t type;
    void(*dealloc)(void*);
} generic_t;

typedef struct {
    uint64_t type;
    void(*dealloc)(void*);
    void* sth_else;
} specific_t;

The common way to copy to the simpler struct would be:

specific_t a = /* some code */;
generic_t b = *(generic_t*)&a;

But this is illegal because it violates strict aliasing rules.

However, if I memcpy the struct I only have void pointers which are not affected by strict aliasing rules:

extern void *memcpy(void *restrict dst, const void *restrict src, size_t n);

specific_t a = /* some code */;
generic_t b;
memcpy(&b, &a, sizeof(b));

Is it legal to copy a struct with memcpy like this?


An example use case would be a generic deallocator:

void dealloc_any(void* some_specific_struct) {
    // Get the deallocator
    generic_t b;
    memcpy(&b, some_specific_struct, sizeof(b));

    // Call the deallocator with the struct to deallocate
    b.dealloc(some_specific_struct);
}

specific_t a = /* some code */;
dealloc_any(&a);
K. Biermann
  • 1,295
  • 10
  • 22
  • 1
    By "legal" you mean "possible"? – andresantacruz Aug 24 '19 at 14:55
  • By legal I mean standard-conformant without the theoretical risk of undefined behavior (provided that both structs have the same internal alignment/padding). – K. Biermann Aug 24 '19 at 14:56
  • The operation is legal and defined by the standard, but the value is implementation-defined. – S.S. Anne Aug 24 '19 at 14:59
  • @K.Biermann I can't see the difference between both cases. By pointer-casting `specific_t ` to `generic_t` you are essentially doing the same as when you do it through `memcpy`. – andresantacruz Aug 24 '19 at 15:02
  • 2
    ["Strict aliasing is an assumption, made by the C (or C++) compiler, that dereferencing pointers to objects of different types will never refer to the same memory location (i.e. alias each other.)"](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule) – If I understand this correctly, casting a `specific_t`-pointer to a `generic_t`-pointer and dereferencing it violates the standard. The only exception to this rule are `void` and `char` pointers. – K. Biermann Aug 24 '19 at 15:10

2 Answers2

3

Legal. According to memcpy manual: The memcpy() function copies n bytes from memory area src to memory area dest. The memory areas must not overlap. Use memmove(3) if the memory areas do overlap.

So it doesn't care about types at all. It just does exactly what you tell it to do. So use it with caution, if you used sizeof(a) instead sizeof(b) you might've overwritten some other variables on the stack.

manish ma
  • 1,706
  • 1
  • 14
  • 19
0

Although this is not a direct answer it may help.

If you want overlapping structs, you could use a union:

typedef union
{
    generic_t  generic;
    specific_t specific;
} combined_t;

This avoids the need to cast. But you need to be very careful to make sure that you don't access sth_else if it's not initialised. You would need data & logic to determine which of the union members has been set plus access functions/macros. This is working towards building class inheritance in C.

In the past I've built a Java-like exception handling mechanism in C (so try, catch, ...). This featured link-time exception 'class' inheritance. So a compiled library could define a SomeException 'class', user code could then 'subclass' this exception, and the new 'subclass' exception would still be caught by a SomeException catch clause. If you need to stay in C, there's a lot you can do with smart macro's and a few well chosen uncomplicated C constructions.

meaning-matters
  • 21,929
  • 10
  • 82
  • 142
  • 1
    But unions are not extensible. If I use a union with `generic_t` and `specific_t` in my library, the library user can't define a custom `user_t` without changing my library. – K. Biermann Aug 24 '19 at 15:07
  • @K.Biermann If your library needs to do something with the newly added type `user_t`, it seems to me it will have to be changed anyway. I miss your extensibility requirements in your question, so I don't fully understand what you want. Could you elaborate? – meaning-matters Aug 24 '19 at 15:22
  • @K.Biermann A `union` remains a good idea for the pre-defined parts of your library. Code can then _extend_ into `combined_t` with `memcpy()`, etc. The question would benefit with more info on how the data is set up and later used. – chux - Reinstate Monica Aug 24 '19 at 15:23
  • 1
    This doesn't answer the question. – S.S. Anne Aug 24 '19 at 19:44
  • @JL2210 You're right it's not giving a direct answer to the question. I've added a starting comment. Yet, my answer tries to respond to what I believe is the underlying issue. It's always good to try and look deeper/further/sideways. – meaning-matters Aug 25 '19 at 11:10