4

I have these two functions:

static inline void *ether_payload(void *pkt)
{
  return ((char*)pkt) + 14;
}
static inline uint16_t ip_id(const void *pkt)
{
  const char *cpkt = pkt;
  uint16_t id;
  memcpy(&id, &cpkt[4], sizeof(id));
  return ntohs(id);
}

Now, there's a type safety issue. For the first function, void pointer means Ethernet header. For the second function, void pointer means IPv4 header. This creates a huge possibility that somebody accidentally calls the second function for an Ethernet header directly. If somebody does so, the compiler gives no warning.

I would like to eliminate this type safety issue through two dummy structs the contents of which are never defined:

struct etherhdr;
struct ipv4hdr;

Now the functions would be:

static inline struct ipv4hdr *ether_payload(struct etherhdr *pkt)
{
  return (struct ipv4hdr*)(((char*)pkt) + 14);
}
static inline uint16_t ip_id(const struct ipv4hdr *pkt)
{
  const char *cpkt = (const char*)pkt;
  uint16_t id;
  memcpy(&id, &cpkt[4], sizeof(id));
  return ntohs(id);
}

This solves the type safety issue. Note I'm not actually accessing the Ethernet or IP headers through a struct which would be very bad practice indeed.

My question is, am I violating strict aliasing rules by defining such an API? Note the data is never accessed via the struct; the data is just accessed via memcpy using a char pointer. My understanding is that char pointer can alias to anything.

Let's leave the fact that Ethernet packet can contain IPv6 as irrelevant, as this was just a very simple example.

juhist
  • 4,210
  • 16
  • 33

2 Answers2

1

As for answering your question, it was already answered by Cornstalks, no, you are not violating any strict aliasing rules.
You may convert a pointer to a char pointer. You may convert char pointer to another pointer if you are sure, that this another pointer is really there. See Strict aliasing rule and 'char *' pointers

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • About your suggestion... if you do typedef void ipv4hdr, then ip4hdr* will be a void pointer, type-compatible with every other void pointer such as etherhdr*. Thus, the dummy structs are necessary. – juhist Apr 10 '18 at 12:46
  • To expand on what @juhist wrote: In your suggestion, mis-use will not give a compiler warning, which was the whole point. – Jann Poppinga Dec 28 '20 at 06:47
1

The Standard allows implementations to impose alignment restrictions for structures which are coarser than those of any items contained therein. This would allow an implementation for a platform that only supports aligned accesses, that was given e.g.

#include <string.h>
#include <stdint.h>
struct foo {uint32_t dat[1]; };
struct bar {uint16_t dat[2]; };
void test1(struct foo *dest, struct foo *src)
{
    memcpy(dest, src, 4);
}
void test2(struct bar *dest, struct bar *src)
{
    memcpy(dest, src, 4);
}

to generate code for test2 which is just as efficient as for test1 [using one 32-bit read and write, instead of two 16-bit reads and writes]. If an implementation were to always pad all structures out to a multiple of four bytes and align them to four-byte boundaries, such an implementation would be allowed to perform the aforementioned optimization on test2 without having to know or care about how or even if struct bar is ever defined anywhere.

I don't know whether any present implementations would ever do such a thing, but I can hardly rule out the possibility that a future implementation might do so since there some circumstances where it could allow more efficient code generation.

supercat
  • 77,689
  • 9
  • 166
  • 211