Grrr
struct { char str[4]; char sc1; char str2[3]; char sc2; } foo __attribute__((packed)); snprintf(foo.str, 5, "%04X", data); foo.sc1 = ';'; snprintf(foo.str2, 4, "%03X", otherdata); foo.sc2 = ';';
Yes, I know that both snprintf() calls in the above snippet will overflow their immediate buffer. Yet this code is safe; the network protocol for which this code is written does not actually need nor expect NUL bytes; instead, it wants semicolons. I could of course use a "char foo[9]" rather than a struct as above, but I find this to be slightly more convenient than to count offsets.
However, this code does not work with glibc, because the buffer overflow detection kicks in.
Solution:
char buf[5]; snprintf(buf, 5, "%04X", data); memcpy(foo.str, buf, 4);
In other words: add a stupid and useless memcpy, because someone thinks they're smarter than me. Stupid morons.
char foo[9]; char *p = foo;
p += snprintf(p, 5, "%04X", data); *p++ = ';'; p += snprintf(p, 4, "%03X", otherdata); *p++ = ';';
A 9-byte struct? Seriously? Why packed? Why not let the compiler pad for you?
struct { union { struct { char str[4]; char sc1; } char for_sprintf[5]; }
Why not just
char foo[10]; snprintf(foo, 10, "%04X;%03X;", data, otherdata);
Instead of the stupid and useless code, just take the minor inconvenience that you mentioned where you just size the array properly:
struct { char str[5]; char str2[4]; } foo attribute((packed));
If you still need access to sc1 and sc2 independently, use a union or whatever.
Try it with a Union, something like:
union { struct { char str[5]; char str[4]; } str; struct { char str[4]; char sc1; char str2[3]; char sc2; } chars; } foo attribute((packed));
snprintf(foo.str.str, 5, "%04X", data); foo.chars.sc1 = ';'; snprintf(foo.str.str2, 4, "%03X", otherdata); foo.chars.sc2 = ';';
yes, not ideal, but that allows you to keep on using glibc's excellent overflow checking help.
include
void main() {
struct { char str[4]; char sc1; char str2[3]; char sc2; } attribute((packed)) foo;
int data, otherdata;
data = 5; otherdata = 42;
snprintf(foo.str, 5, "%04X", data); foo.sc1 = ';'; snprintf(foo.str2, 4, "%03X", otherdata); foo.sc2 = ';';
}
char foo[9]; snprintf(foo, sizeof(foo), "%04X;%03X", data, otherdata); foo[8] = ';';
Because this is sent over the wire. It needs to be packed, otherwise we implement the protocol incorrectly.
Also, this is an example. The real, actual struct is similar, but different.
I tried to reproduce this (with the following code) I found that the attribute((packed)) was getting ignored because it's in the wrong order. It needs to come before foo, not after.
include
include
struct { char str[4]; char sc1; char str2[3]; char sc2; } foo attribute((packed));
int main(int argc, char** argv){ snprintf(foo.str, 5, "%04X", 1000000); foo.sc1 = ';'; snprintf(foo.str2, 4, "%03X", 100000); foo.sc2 = ';'; return 0; }
test.c:9: warning: ‘packed’ attribute ignored
I should perhaps clarify something.
The code above is a reworked example of the actual code that I'm using. This is because there's no good reason to dump a whole privately-used network protocol on the public internetz, and the format of the struct wasn't essential to the discussion at hand (why else do you think I'd be using "foo" as a variable name?)
The actual code did not have the declaration and use of the struct in the same line of code, so apparently I messed up there; but it was a correct and working packed struct that hit this problem.