Struct with unaligned fields

Discussion in 'C Programming' started by James Harris, Aug 22, 2013.

  1. [...]

    On a SPARC, this program:

    #include <stdio.h>
    int main(void)
    {
    struct foo {
    char c;
    int x;
    } __attribute__((packed));
    union u {
    struct foo f;
    long double align;
    };
    union u obj = { { 'a', 10 } };
    int *ptr = &obj.f.x;
    printf("obj.f.x = %d\n", obj.f.x);
    fflush(stdout);
    printf("*ptr = %d\n", *ptr);
    return 0;
    }

    produces this output:

    ptr = ffbff351
    obj.f.x = 10
    Bus error

    (I needed the union to force the `struct foo` object to be word-aligned;
    without it, the compiler places it at an odd address, causing the `int x`
    member to be word-aligned.)
     
    Keith Thompson, Aug 24, 2013
    #41
    1. Advertisements

  2. James Harris

    Eric Sosman Guest

    One RS/6000 system I recall did something even worse than
    crashing: The hardware simply ignored the offending low-order
    address bits, "rounding down" to the nearest aligned address.
    Thus, it buzzed happily along (at full speed), having read or
    written the wrong object ...

    That was many years ago, and I don't know if the system's
    descendants (many times renamed) still behave that way.
     
    Eric Sosman, Aug 24, 2013
    #42
    1. Advertisements

  3. James Harris

    James Harris Guest

    ....
    I'd like to find out some more about this. What would be a good search term?

    James
     
    James Harris, Aug 24, 2013
    #43
  4. James Harris

    Les Cargill Guest

    Thanks, Keith. Such sadistic hardware!
     
    Les Cargill, Aug 24, 2013
    #44
  5. Sadistic? I don't think so.

    My understanding is that supporting misaligned access requires
    extra work in designing the CPU hardware. Not supporting it left
    more resources available, both in design effort and chip area,
    to support other features (lots of registers, for example) and
    better performance.

    And 90+% of the time, keeping operands properly aligned isn't much
    of a problem anyway; the compiler takes care of it for you.

    Perhaps the tradeoffs have changed since SPARC was designed, and
    supporting unaligned access makes more sense now. But you can see
    from my (snipped) example how convoluted your code has to be to
    trigger a problem.
     
    Keith Thompson, Aug 24, 2013
    #45
  6. James Harris

    Ian Collins Guest

    Yes, I use something similar (a generic template with fewer casts!)
    except I use a pointer to the start byte rather than individual chars.
    Using a pointer to the start removes this minor risk.
     
    Ian Collins, Aug 24, 2013
    #46
  7. James Harris

    Tim Prince Guest

    Several current compilers use split moves to handle what appear to be
    mis-aligned access, in case the hardware support may be even worse than
    using multiple instructions, even though neither choice will fail outright.
    For example, initial implementations of AVX-256 perform better when
    mis-aligned 256-bit moves are split into 128-bit move instructions. It's
    impractical to sort out whether a newer hardware platform is running
    which would perform better with a 256-bit unaligned move.
    Current 512-bit wide architectures support mis-alignment only by the
    compiler choosing split moves, at a performance penalty.
     
    Tim Prince, Aug 24, 2013
    #47
  8. Ah, I figured this had to be possible but didn't think of overloading
    those particular operators; that's clever.
    Why not make that dependency explicit by using uint8_t and uint32_t
    instead of unsigned char and unsigned long? That also has a side
    benefit of dealing with machines that have a 64-bit unsigned long.
    Lack of padding is not guaranteed, true, but alignon(nbou4) is almost
    certainly 1, so why would an implementation insert padding? I've never
    heard of one that would.

    S
     
    Stephen Sprunk, Aug 24, 2013
    #48
  9. James Harris

    Les Cargill Guest

    I forgot to smiley. Here: :)
    So they push this off to the toolchain. But the toolchain
    doesn't support it...
    I believe that was the original point - that GNU offers
    "#prgama pack" and that I've see it work. Seems the best approach,
    if it was available.
    Certainly. In this case, it appears to be a vestige of formats
    developed under 8-bit machines.
     
    Les Cargill, Aug 24, 2013
    #49
  10. James Harris

    Ian Collins Guest

    In the case of SPARC, yes it does, but at a significant performance
    cost. Even with the appropriate hardware support, misaligned access is
    still a performance killer.
     
    Ian Collins, Aug 24, 2013
    #50
  11. It does, mostly; gcc supports #pragma pack. (The native C compiler
    also supports #pragma pack. Oddly, my test program doesn't crash
    when compiled with Sun C 5.9; I haven't yet figured out why.)
    As long as you refer to an misaligned member by name, the compiler
    will do whatever is necessary to access it correctly. It only
    causes problems if you go behind the compiler's back by grabbing
    a pointer to a misaligned member.

    One solution might be to create a compiler-specific attribute for
    pointer types, so that taking an `int*` address of a misaligned
    member is illegal, but taking a `__misaligned int*` (or whatever
    syntax you prefer) address is legal -- and dereferencing such a
    pointer does some extra work.

    Another point in favor of requiring strict alignment: If you do
    it accidentally (say, by treating a slice of a char array as an
    int), performance is likely to suffer, even on an x86. By making
    misaligned access a fatal error, you can catch such problems sooner.
    I'm not saying that's necessarily enough of a reason by itself to
    require strict alignment.

    [...]
     
    Keith Thompson, Aug 25, 2013
    #51
  12. James Harris

    Tim Rentsch Guest

    Taking this approach will almost certainly lead to undefined
    behavior caused by not following effective type rules. It
    is always allowed to access an object of any effective type
    using an lvalue of character type, but not vice versa. Of
    course it's possible there will be no adverse manifestation
    of the UB, but it's not a good idea to put yourself in the
    position of having to rely on that.
     
    Tim Rentsch, Aug 25, 2013
    #52
  13. James Harris

    Tim Rentsch Guest

    If you do go down this path, be warned that accessing
    the "split" fields using casted pointer values easily
    may wander off into undefined behavior, even on machines
    with no alignment issues, because of problems with
    effective type rules. Basically, if you declare a field
    to be of character type, it must be accessed using only
    character lvalues (not counting assigning the whole struct
    or something like that).
     
    Tim Rentsch, Aug 25, 2013
    #53
  14. James Harris

    Les Cargill Guest

    I am no longer certain why so much was said about
    alignment issues, then.
    Not too bad an approach.
     
    Les Cargill, Aug 25, 2013
    #54
  15. James Harris

    Ian Collins Guest

    Because there is only one member (the pointer)!
     
    Ian Collins, Aug 25, 2013
    #55
  16. Yes and yes.
    The original 16-bit Tandem NonStop, and I believe its predecessor
    HP3000, had different format pointers to byte (char) and everything
    larger (word=2bytes or more). They used 'word' pointers for structs,
    so all structs had to be word = 2-byte aligned. This only mattered for
    struct-in-struct, because all top-level variables were word-aligned
    anyway. When they extended to 32-bit NonStop II aka NS2 they added
    32-bit all-byte pointers, but included the original instructions and
    pointers, retronymed NS1+ (the plus was some marketroid's invention,
    nothing was actually added), so to allow pointer conversions to work
    as long as you stayed in the 16-bit addressable area (essentially the
    stack and "low" static and heap, the compiler default and thus an easy
    rule to follow) structs still had to be 2-byte aligned.

    "NS1+" and NS2 hardware is gone now, but TNS was mostly used for
    enterprise-critical systems like airline reservations and bank ATMs.
    The owners of these systems are reluctant and slow to make changes
    whose failure might cost billions of dollars, so successor hardware
    today -- commodity byte RISC -- can still run 30-year-old NS2 and even
    NS1 code, nearly matching IBM's better-known S/360 clan.
     
    David Thompson, Aug 29, 2013
    #56
  17. Ah; that's interesting. Does the same hold for other word-addressed
    systems, or is it peculiar to the above example?

    S
     
    Stephen Sprunk, Aug 29, 2013
    #57
  18. I was aware of special char-offset pointers; I just hadn't heard of
    structs (even ones with only char members) having to be aligned on a
    word boundary.

    S
     
    Stephen Sprunk, Sep 2, 2013
    #58
  19. To be clear re the below: TNS1 pointers were the same *size*, 16 bits.
    Byte pointer was high 15 bits select word and low bit select byte,
    word pointer was plain 16 bits, but the compilers+linker put variables
    and the hardware put the (upward!) stack in 00000-77777; 100000-177777
    was used by the language runtimes (things like FILE blocks in C) and
    if needed (rarely) could also be manually allocated/managed.
    PDP-10 (and PDP-6) had an interesting variant on this. It had 36-bit
    word and most instructions accessed a word using a 18-bit address
    (optionally with index/offset and indirect); one special group of
    instructions accessed the left or right half-word (selected by the
    instruction, at compile time); and one special group of instructions
    could access *any* bit-field from 1 bit up to the whole word, using a
    'byte pointer' with the word address in the right half like a normal
    pointer, and the bit offset and bit width in the left half. Since C
    requires that chars be at least 8 bits, fixed, and a u-char array
    access all bits of memory, it could only use 9, 12, or 18 bit bytes.
    AFAIK there was no C for PDP-10 back in the 1970s (when C and even
    Unix were mostly viewed as a curious experiment); there was talk a few
    years ago in alt.sys.pdp10 about some people doing a gcc target (there
    are still a few hardware machines going, and also good simulators) and
    I'm pretty sure they used 9.
     
    David Thompson, Sep 4, 2013
    #59
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.