2

It's sometimes necessary to cast a data structure into a pointer so that the data can be sent, for example, over an interface, or written out to some other stream. In these cases, I usually do something like this:

typedef struct {
  int    field1;
  char   field2;
} testStruct;

int main()
{
  char *buf;
  testStruct test;

  buf = (char *)&test;

  // write(buf, sizeof(test)) or whatever you need to do

  return 0;
}

Recently in some microprocessor code, however, I saw something similar to this:

typedef struct {
  int    field1;
  char   field2;
} testStruct;

int main()
{
  char buf[5];
  testStruct test;

  *(testStruct *)buf = test;

  // write(buf, sizeof(test)) or whatever you need to do

  return 0;
}

To me, the former feels a little more safe. You just have one pointer, and you assign the address of the structure to the pointer.

In the latter case, it seems like if you allocate the wrong size to the array buf by accident, you'll end up with undefined behavior, or a segfault.

With optimizations on, I get a -Wstrict-aliasing warning from gcc. However, again, this code runs on a microprocessor, so is there something I might be missing there?

There's no pointers in the structures, or anything, it's very straight forward.

9
  • Two questions before yours there was this one: stackoverflow.com/questions/48571295/… Commented Feb 1, 2018 at 21:43
  • This question has nothing to do with memcpy or deep copies in general. It's more about the correct way to cast a data structure to a buffer. Commented Feb 1, 2018 at 21:45
  • 1
    (testStruct *)buf may generate a mis-aligned address for a testStruct leading to a bus fault. Do not use. A union is better. Commented Feb 1, 2018 at 21:47
  • 2
    write and friends use a void * as a buffer argument so no cast is needed. write(fd, &test, sizeof(test)) is perfectly OK, as long as you accounted for platform differences. *(testStruct *)buf = test; wouldn't pass a code review. Commented Feb 1, 2018 at 22:00
  • 1
    Besides running the reasonably grave risk of misaligned data accesses, *(testStruct *)buf = test is a bad idea (actually a pretty stupid idea, I'd say) because it needlessly copies data. If you have test, but you just want to temporarily treat it as a blob o' bytes, then the equivalent of buf = (unsigned char *)&test is just what you want. (Or if you really want to copy data, call memcpy. But a cast and a pretend struct assignment is just a bad idea, a holdover from the rough, roguish early days of C.) Commented Feb 1, 2018 at 22:05

1 Answer 1

2

(testStruct *)buf may generate a mis-aligned address for a testStruct leading to a bus fault. Do not use.

A union is better. It helps cope with anti-aliasing issues as well as alignment ones.

Also see @Steve Summit's good comment.

Consider a master type like testStruct_all.

typedef struct {  // OP's structure
  int    field1;
  char   field2;
} testStruct1;

typedef struct {  // Perhaps another structure to send
  double field1;
  char   field2;
} testStruct2;

// A union of all possible structures used in this app
typedef union {
  testStruct1 tS1;
  testStruct2 tS2;
  char buf[1]; 
} testStruct_all;

int main(void) {
  testStruct_all ux; 
  foo(&ux.tS1);  // populate ux.tSn of choice.

  write(ux.buf, sizeof ux.tS1);

  read(ux.buf, sizeof ux.tS1);
  // the union insures alignment and avoids AA issues
  bar(&ux.tS1);
  return 0;
}

write() usually accepts a void * @user58697, so code could drop the buf member and use:

  write(&ux, sizeof ux.tS1);  //  or whatever you need to do
Sign up to request clarification or add additional context in comments.

6 Comments

sizeof(double) != sizeof(int) on most systems. Did you mean something like int64_t and double? Or were you intentionally pointing out how this approach resolves references to structs with different sizes?
Ignore the write() call. It's a microprocessor, what if I assign the pointer to a SPI tx register and latch it out with interrupts or something? It was more about the deserialization/casting than the call it's going in to.
@PatrickRoberts Yes, the idea was to show how the structs, char[] may differ in size and alignments needs yet with a union, all is aligned and as a union by-passes AA issues.
@justynnuff (testStruct *)buf can easily fail a uP that requires an int on even boundaries, yet allows a char[] to exists on even/odd boundaries.
You're question is a correct answer. But I keep thinking that whoever wrote this code wasn't that stupid. By doing *(testStruct *)buf = test; into a buf that has been allocated memory, I wonder if that was a way that they were doing memcpy on a uP/compiler that maybe didn't have memcpy at the time?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.