Syntax for union parameter

Discussion in 'C Programming' started by Rick C. Hodgin, Jan 29, 2014.

  1. I have had occasion where I've needed to do something like this:

    struct SBGRA
    {
    unsigned char blu;
    unsigned char grn;
    unsigned char red;
    unsigned char alp;
    };

    void main(void)
    {
    struct SBGRA color = { 255,255,255,255 }; // white, full alpha
    foo(color); // do something with that color
    }

    void foo(SBGRA color)
    {
    union {
    struct SBGRA color;
    unsigned int _color;
    } u;

    // Convert SBGRA to an unsigned int
    u.color = color;
    some_other_function(u._color); // Do something with that number
    }

    -----
    Is there a way to do something like this in C (to declare a union in
    the function definition line)?

    void foo(union { SBGRA color, unsigned int _color } u)
    {
    some_other_function(u._color);
    }

    There are only certain occasions where I need it, and rather than declare
    something global, it would be nice to be able to declare the local union
    in the definition to allow existing data passed in the structure format
    to continue unchanged, but then to access it without the extra local
    union definition, and in non-structure form.

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #1
    1. Advertisements

  2. This should be int main(void)
    You're creating an anonymous type that's visible only inside the
    function. How are you going to pass a value of type

    union { SBGRA color, unsigned int _color }

    to foo? You can create another identical type, but it's still going to
    be distinct from the one defined in the parameter list.

    Just define the union type so it's visible to the function and to any
    callers.
     
    Keith Thompson, Jan 29, 2014
    #2
    1. Advertisements

  3. Ideally? :) As either SBGRA or unsigned int. The compiler would/
    should recognize either as being valid and fulfilling the calling
    parameter requirements.
    I thought that might be in.
    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #3
  4. Rick C. Hodgin

    Kaz Kylheku Guest

    This trick is quite nonportable. Even if the elements of the structure
    are packed (which they might not be) and so that they are laid out like an
    unsigned int (which might not be four bytes), there is still the question of
    byte order. Is "blu" at the low-end of the unsigned int, or high end?

    A more portable approach is to do some shifting and masking arithmetic
    to create a 32 bit value out ouf the four 8 bit values:

    unsigned long SRGBA_to_scalar_color(SRGBA srgba)
    {
    return ((unsigned long) srgba.alp) << 24 |
    ((unsigned long) srgba.red) << 16 |
    ((unsigned) srgba.grn ) << 8 |
    (unsigned) srgba.blu;
    }

    From this basis we can optimize.

    #if CONFIG_SUCH_AND_SUCH_PLATFORM && CONFIG_SUCH_AND_SUCH_ASSUMPTION
    unsigned long SRGBA_to_scalar_color(SRGBA srgba)
    {
    /* some faster code not based on shifting and masking*/
    }
    #elif CONFIG_SOMETHING_ELSE
    unsigned long SRGBA_to_scalar_color(SRGBA srgba)
    {
    /* some different faster code not based on shifting and masking*/
    }
    #else
    /* stick the above portable function here */
    #endif
    The parameter then has a type which is only known in the scope of the
    parameter declaration. This means that there is no way to write a call
    to this function that doesn't require a diagnostic (and likely stops
    compilation).

    However, though not achieving quite the above slickness, you can
    do something like this: type punning between a union and struct:

    void foo(SGBRA color c)
    {
    typedef union { SBGRA color; unsigned int color_word; } USGBRA;
    USGBRA *pu = (USGBRA *) c;
    some_other_function(pu->color_word);
    }

    This may not be in a well-defined area of behavior, since we have not
    actually stored a value into one member of a union and retrieved
    another, but simply overlaid a union alias on a struct.

    If you do this kind of type punning on GCC then compile (at least that source
    file) with -fno-strict-aliasing.
     
    Kaz Kylheku, Jan 29, 2014
    #4
  5. I suspect one reason this is not done is that it is not in general
    possible to tell just form the argument expression which union member is
    to be initialised. However, see below...
    And then your old friend compound literals come in handy again, this
    time with anther C99 feature: designated initialisers. If you have

    union colour { struct scolour colour_s; unsigned int colour_i; }

    void some_function(union colour c) {...}

    You can pass union objects without the need to name them:

    struct scolour cs = {...};

    some_function((union colour){ .colour_s = cs });
    some_function((union colour){ .colour_i = 42 });
    some_function((union colour){ .colour_s = {9, 8, 7, 6} });

    You can, of course, also use this technique to convert from one form to
    the other:

    printf("%x\n", (union colour){ .colour_s = {1, 2, 3, 4} }.colour_i);
     
    Ben Bacarisse, Jan 29, 2014
    #5
  6. I love compound literals. :)

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #6
  7. No. SBGRA, unsigned int, and your anonymous union are three distinct
    types, and there are no implicit conversions between the union type and
    the types of its members.

    [...]
     
    Keith Thompson, Jan 29, 2014
    #7
  8. Rick C. Hodgin

    BartC Guest

    I have a thing for anonymous structs and unions. So I would probably deal
    with this by building the _color field into the original struct:

    #define byte unsigned char

    struct SBGRA
    {
    union
    {
    struct
    {
    byte blu;
    byte grn;
    byte red;
    byte alp;
    };
    unsigned colour;
    };
    };

    struct SBGRA c={127,191,255,255};

    printf("%d %d %d %d\n",c.blu,c.grn,c.red,c.alp);
    printf("%X\n",c.colour);

    However I don't know how widely this feature is supported.

    (BTW SBGRA isn't the easiest name to get right. It took ten minutes puzzling
    over compilers errors, when I mistyped the second one as SBRGA. Of course
    the compiler couldn't just tell me that SBRGA was undefined, just 'storage
    size not known', 'too many initialisers' etc.)
     
    BartC, Jan 29, 2014
    #8
  9. I realize that's how it works today. It's my wish list thinking there.
    I will implement this union parameter feature in RDC.

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #9
  10. Using the relaxed syntax allowances of Visual C++, I have something
    similar (line 810):

    https://github.com/RickCHodgin/libsf/blob/master/vvm/core/common/common.h

    May I introduce you to the Visual Studio IDE? If you use the Whole Tomato
    plugin called "Visual Assist X" it will handle auto-suggestions for you.
    Simply type "SB" and press tab and it fills it in. In addition, the code
    preview window is auto-populated with the source line of the definition so
    you can immediately see its members at a glance.

    You can see me coding here (1920 x 1200 scaled down for the video):
    http://www.visual-freepro.org/videos/

    And specifically here you can see how the Visual Studio IDE works (the
    music is the 2006 Celtic Woman performance at Slane Castle I think):
    http://www.visual-freepro.org/videos/2013_02_14__01_rxml_development.ogv

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #10
  11. For the record, it's official C11 syntax, so it can be accepted by
    uptight compilers too.

    <snip>
     
    Ben Bacarisse, Jan 29, 2014
    #11
  12. Rick C. Hodgin

    James Kuyper Guest

    The standard allows an implementation to insert arbitrary padding
    between members of a struct. This is extremely unlikely when the members
    are all of character type, because there's no possibility of alignment
    issues that would require padding. However, since your code absolutely
    depends upon the absence of such padding, you really should be using a C
    construct which actually forbids padding, such as unsigned char[4],
    rather than a struct.
    The return type of main must be int. Many text books get this wrong, and
    many people write their code accordingly, and as a result many compilers
    accept such code - those books and those people are all ignoring what
    the C standard says. Those compilers can actually be fully conforming -
    the behavior of such a program is undefined, which means it's perfectly
    acceptable for the implementation to translate it the way you've
    incorrectly assumed it must be translated.. However, a decent compiler
    should warn you about this mistake.
    There are many contexts where you're prohibited from defining an
    identifier that starts with '_'. This is NOT one of those contexts.
    However, can you tell me precisely when it is safe to define such an
    identifier? If not, I recommend avoiding them entirely, at least until
    you've had time to memorize the relevant rules (section 7.1.3 of the C
    standard).

    From past experience with you, I doubt that you're concerned about
    portability - but the C standard provides no guarantees that unsigned
    int is long enough to alias struct SBGRA, even if it had no padding.
    unsigned char is allowed to have more than 8 bits, and there are real,
    modern machines where it has 16 bits. unsigned int is allowed to have as
    few as 16 bits, and there are real, modern machines where it is. You
    should use uint8_t and uint32_t, respectively, from <stdint.h>. Those
    are optional types, but it's rather unlikely that either of them is
    unsupported. On any platform where either one is not supported, nothing
    remotely similar to what you've written will work, so it's a good idea
    to write your code in such a way that it would fail at compile time with
    a diagnostic on such a platform.

    There's also another and much more plausible portability issue, which
    you might be equally unconcerned about. Even assuming you are using
    uint8_t[4] and uint32_t, there's still no guarantees about which bits in
    u._color correspond to each element of u.color. In particular, there's
    the big-endian/little-endian issue. If you need to port this code
    between big-endian and little-endian machines, you'll need to decide
    whether the byte order imposed by u.color or the bit order of u._color
    is more important. I would expect the byte order of u.color is the
    relevant one, and I'll use that assumption below.
    In order to call foo() with defined behavior, you must pass it an object
    of a type compatible with the unnamed type of u. Since that type is
    unnamed, that is, in fact, extremely difficult (but possible) to
    arrange. I wrote up a long explanation of why it was difficult, and how
    it could be arranged, with the net conclusion that it makes much more
    sense to give up your insistence that the union be anonymous. It is far
    easier to make foo() usable if you give the union either a tag name or a
    typedef.

    However, after putting a lot of effort into that explanation, I realized
    that it's probably irrelevant, because I'm almost certain that you don't
    want to create an object of compatible type to pass to foo(). I suspect
    that you want to pass either a struct SBGRA or an unsigned int to foo(),
    possibly both, and neither of those types is compatible with the type of
    u. You can't make anything like that work portably in C.

    There's probably many implementations where you could get it to work if
    you use a K&R style declaration for foo() rather than a function
    prototype, in any translation unit where foo() is called. Note that the
    definition of foo() given above includes a function prototype:
    therefore, if you wish to call foo() from later on within the same
    translation unit where it is defined, you should change the definition
    accordingly. I would not recommend that approach, but if you're
    completely unconcerned with portability, go ahead and give it a try, it
    might work (or it might not).

    The right way to do this is to pass around uint32_t values, and then
    using constructs such as ((uint8_t*)color)[2] to access the red byte of
    the object that it's stored in.
     
    James Kuyper, Jan 29, 2014
    #12
  13. I always use whatever compiler setting is required to align everything to
    bytes. If I need a particular struct aligned for some reason I use a
    struct-instance override (such as #pragma align 16).
    I used it only to illustrate. I noticed in one of the coding examples
    posted to me a void main(void) declaration. I had been using the ones
    I am familiar with int main(int argc, char* argv[]) and it's a lot of
    typing. So, for illustration I did the shorter version. :)
    I use them in all cases like this, and for global constants. All constants
    in my systems are prefixed with an underscore, and are all caps. It's a
    convention I picked up from my assembly days and have just kept.
    I don't actually use int in my code. I have done that here so people can
    paste the code into their editors and test it. I have indicated previously
    that I use explicit types:

    s8, u8 -- signed, unsigned 8-bit quantity
    s16, u16
    s32, u32
    s64, s64
    f32, f64 -- floating point 32-bit float or 64-bit double

    So in my code in lieu of int I would use s32 or u32 depending on needs.
    By using s32, u32, and so on, I can need only change the header lines as
    they exist in this file at/near the top:

    https://github.com/RickCHodgin/libsf/blob/master/vvm/core/common/common.h
    My targets are x86 and ARM. They both support little endian. I doubt I
    will ever support a big endian machine. And if I do, there are handful
    of places where I can modify my code IF AND WHEN that ever happens.
    I view the compiler as simply needing to create two references when it
    sees a union like this:

    void foo(SBGRA color)
    void foo(int _color)

    And then auto-insert code which populates the value, or simply recognizes
    it as it is used within as through the union. I realize it does not do
    this today, however, that's how I see it needing to be done.
    Understood. And I appreciate your efforts (FWIW).
    I have no desire for portability in many cases. It greatly increases the
    workload for something that today does not exist in my plans at all. As
    I have stated, I am planning to complete my RDC and I will support my
    syntaxes without change on every platform I generate code for. As such,
    portability is sort of "built in" in that regard. :)
    I view that as something that's absolutely non-intuitive and syntactically
    clumsy. Using u.color.red makes sense.

    I think actually in my compiler I would allow this:

    void foo(union { SBGRA color, unsigned int _color })
    {
    printf("R:%u G:%u B:%u A:%u\n", color.red, color.grn, color.blu, color.alp);
    printf("Color:%u\n", _color);
    }

    I would not require the "u" reference at all Both color and _color would
    point to the same memory location on the stack.

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #13
  14. I use Visual Studio 2008. It knows nothing of C11 syntax and must be
    deriving these relaxations from its C++ nature, or Microsoft allowances.

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #14
  15. Rick C. Hodgin

    Eric Sosman Guest

    So *all* your constants infringe on reserved name space?
    How very jolly ...

    Also, the fact that you did things one way in assembly
    language -- or in Java or Ada or PL/I or COBOL -- is not a
    good reason to do things the same way in C or Lisp or Fortran
    or Lua or Perl.
     
    Eric Sosman, Jan 29, 2014
    #15
  16. The return type of main must be int.[/QUOTE]

    How about that "casting the return value of malloc()" thing?
    How's that working out for you?
    Wrong. If a compiler documents that "void main()" is OK, then it is OK.

    Compilers are allowed to extend the C standard.

    The position held by the CLC religion, on this and many similar points, is
    that "Well, you should always do it this way, because this way works all
    the time [is guaranteed to work by the C standards documents] and the other
    way(s) may not work [although they do work in almost all cases, so the
    point is largely moot]". This is the normal "better safe than sorry"/"best
    practices" position - and is held, religiously one might say, by, among
    others, your good friend Kiki. Kiki is very good at limiting himself to
    this position and not over-stepping by insisting that any other way is
    categorically wrong. But, as we see in your case, it is very easy to go
    too far and insist that "void main()" is wrong.

    This sort of thinking is, incidentally, common to most major religions,
    where what a small group of people think of as "best practice" is somehow
    elevated to the status of "God's word".

    --
    The problem in US politics today is that it is no longer a Right/Left
    thing, or a Conservative/Liberal thing, or even a Republican/Democrat
    thing, but rather an Insane/not-Insane thing.

    (And no, there's no way you can spin this into any confusion about
    who's who...)
     
    Kenny McCormack, Jan 29, 2014
    #16
  17. And, more importantly in the context of this newsgroup, by uptight posters
    as well.
     
    Kenny McCormack, Jan 29, 2014
    #17
  18. Reserved in C/C++. My target is not C/C++, but RDC. It has been my target
    since the mid-90s. I just haven't gotten there yet.
    If not ... what is a good reason (given the constraints of: (1) it is
    allowed today by the compiler, (2) it does not cause any issue with my
    code in source or executable form, (3) my goals are RDC and not C/C++,
    and (4) it becomes part of a standard throughout the toolchain
    (anything with an underscore and all caps is a constant, and anything
    with an underscore indicates it's not the real thing, but a reference
    to something else as through a union))?

    Best regards,
    Rick C. Hodgin
     
    Rick C. Hodgin, Jan 29, 2014
    #18
  19. Rick C. Hodgin

    James Kuyper Guest

    int main() is even shorter, and unlike void main(), it has defined
    behavior on all hosted implementations of C.

    ....
    Stop using that convention in C. C reserves some identifiers for the
    implementation, and other identifiers for the users, in order to make
    sure they don't interfere with each other. It is exactly analogous to
    reserving one side of the road for northbound traffic, and the other
    side for southbound traffic. To make the analogy exact, both sides of
    the road are very wide, so what you're doing is less dangerous than
    driving north in a southbound lane; but it's still an unnecessarily
    dangerous thing to do.

    Do you routinely drive on the wrong side of the road, too? If so, and
    assuming that you survive long enough to be caught, would you tell the
    police officer "That's OK, I'm planning to build my own road network
    where all the streets allow you to go in either direction"? I don't
    think the police officer would accept that explanation; neither would
    the judge. I also don't think, if you ever get that road network built,
    that there's many sane people who'd want to drive on it.

    ....
    That has nothing to do with the way C works today. It may be the way
    that your new computer language will work, but this is not the best
    forum for discussing code that relies upon it working that way.

    ....
    This is not the best place to discuss your plans for RDC. Why do you
    persist in doing so?
     
    James Kuyper, Jan 29, 2014
    #19
  20. Rick C. Hodgin

    James Kuyper Guest

    Then don't discuss it here. Discuss it in a forum devoted to discussing
    new languages. You'll find many people with similar interests in such a
    forum, able to give you better advice than people in this forum.
     
    James Kuyper, Jan 29, 2014
    #20
    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.