Thread: Memory pools, object lifetimes and strict aliasing

  1. #31
    Registered User
    Join Date
    May 2014
    Posts
    121
    The example with the union that Codeplug posted seems very dubious to me. I recommend reading this defect report: Fixing the rules for type-based aliasing

    This paragraph in particular is important:
    The committee's decision concerning DR 236 was that the form of an lvalue is significant in determining what operations on a union are valid. Accesses to a union member through a pointer to non-union type are not allowed to change the identity of the last-stored member of the union; changing the "effective type" of the union is allowed only where the appropriate union member's name actually appears in the expression effecting the change. For example

  2. #32
    Registered User
    Join Date
    May 2014
    Posts
    121
    I've read the entire C99 and C11 standard in an attempt to figure this out and I still don't get it.

    Consider this:
    The effective type of an object for an access to its stored value is the declared type of the object, if any. If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value. If a value is copied into an object having no declared type using memcpy or memmove, or is copied as an array of character type, then the effective type of the modified object for that access and for subsequent accesses that do not modify the value is the effective type of the object from which the value is copied, if it has one. For all other accesses to an object having no declared type, the effective type of the object is simply the type of the lvalue used for the access.
    Footnote from the standard:
    Allocated objects have no declared type.
    The strict aliasing rule prevents you from accessing objects unless you're using a type that is compatible with the effective type of the object. The problem with that is that once you've specified the effective type for allocated memory then you can only change the effective type by using memcpy or memmove.

    I do not find anything in the standard that supports the line of reasoning that grumpy has taken with regards to peeking etc but he may be right in practice if you consider what the current compilers are actually doing. Please point out if I have missed anything in the standard related to this.

  3. #33
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by MOS-6581 View Post
    The strict aliasing rule prevents you from accessing objects unless you're using a type that is compatible with the effective type of the object. The problem with that is that once you've specified the effective type for allocated memory then you can only change the effective type by using memcpy or memmove.
    That's not true. You're reading more into that clause than warranted. It is not saying the only way the effective type of an object can change is by using memcpy() or memmove(). The various usages of the term "lvalue" are describing other ways.

    Quote Originally Posted by MOS-6581 View Post
    I do not find anything in the standard that supports the line of reasoning that grumpy has taken with regards to peeking etc
    You also would not have found anything in the standard which contradicts that line of reasoning.
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  4. #34
    Registered User
    Join Date
    May 2014
    Posts
    121
    Quote Originally Posted by grumpy View Post
    That's not true. You're reading more into that clause than warranted. It is not saying the only way the effective type of an object can change is by using memcpy() or memmove(). The various usages of the term "lvalue" are describing other ways.
    I guess you are talking about this:
    If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value.
    What does the bolded part mean? That you can change the effective type of an allocated object simply by modifying it since the object doesn't have an effective type for accesses that modify it? That makes sense.
    Code:
    void *mem = malloc(sizeof(int) + sizeof(float));
    int *x = mem; *x = 42; // The effective type of mem is now an int
    float *y = mem;
    printf("%f\n", *y); // Not legal, the effective type that y points to is an int
    float *y = mem; *y = 42; // Legal, the effective type of mem is now a float.
    free(mem);
    Does that look right?

    As far as "peeking" is concerned I recommend that you read this defective report: Defect Report #236

  5. #35
    Registered User
    Join Date
    Jun 2005
    Posts
    6,815
    Quote Originally Posted by MOS-6581 View Post
    What does the bolded part mean? That you can change the effective type of an allocated object simply by modifying it since the object doesn't have an effective type for accesses that modify it?
    I would remove everything from the "since", but yeah.

    Quote Originally Posted by MOS-6581 View Post
    As far as "peeking" is concerned I recommend that you read this defective report: Defect Report #236
    It appears that defect report is working on the premise that "peeking" is not required, with this "(This function can be in translation unit of its own and have no knowledge about the union or the allocated object.)".
    Right 98% of the time, and don't care about the other 3%.

    If I seem grumpy or unhelpful in reply to you, or tell you you need to demonstrate more effort before you can expect help, it is likely you deserve it. Suck it up, Buttercup, and read this, this, and this before posting again.

  6. #36
    Registered User
    Join Date
    May 2014
    Posts
    121
    Quote Originally Posted by grumpy View Post
    I would remove everything from the "since", but yeah..
    Yeah I realize that the wording of that part was not entirely accurate since the type of the lvalue accessing the object will set the effective type for an access that modifies the object. What I meant was that whatever effective type the object had before that access won't matter as long as it's an allocated object we're talking about.

    Quote Originally Posted by grumpy View Post
    It appears that defect report is working on the premise that "peeking" is not required, with this "(This function can be in translation unit of its own and have no knowledge about the union or the allocated object.)".
    It's perfectly clear to me now why the compiler is allowed to do what it did in that example. I did some experimentation in Visual Studio and noticed even stranger results from much simpler code so breaking the strict aliasing rule really can lead to unpredictable behaviour.

    I'm still not sure what allows placement new to create objects (int, float, etc) in a char[] that wasn't dynamically allocated though. It's definitely not legal for other code to reinterpret that char[] as anything except a (signed or unsigned) char object.
    Last edited by MOS-6581; 05-24-2014 at 07:29 PM.

  7. #37
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> Your example looks very similar to what's going on this blog post: Embedded in Academia : C Puzzle: Double Trouble
    Not impressed. Both examples have undefined behavior. Mine does not.

    gg

  8. #38
    Registered User
    Join Date
    May 2014
    Posts
    121
    Quote Originally Posted by Codeplug View Post
    Not impressed. Both examples have undefined behavior. Mine does not.
    This defective report may indicate that your example isn't or at least shouldn't be legal: Fixing the rules for type-based aliasing
    The committee's decision concerning DR 236 was that the form of an lvalue is significant in determining what operations on a union are valid. Accesses to a union member through a pointer to non-union type are not allowed to change the identity of the last-stored member of the union; changing the "effective type" of the union is allowed only where the appropriate union member's name actually appears in the expression effecting the change.
    That's almost exactly what you're doing; you're trying to change the effective type of the union through a pointer of non-union type. Whether or not something should be illegal simply because the C committee intended it to be is not for me to say.

    The example in that report specifically mentions that it's okay to change the effective type through an union pointer to &g_mem but it's not clear if you're allowed to to do it through an int or float pointer to &g_mem.

    The following isn't allowed so why should it be allowed to do it through a pointer?
    Code:
    union X {int x; float y;} x;
    x = 5; // Illegal, "cannot convert from 'int' to 'X'"
    int *p = &x;
    *p = 42; // Why should this be legal?
    It just so happens that Visual Studio doesn't complain about the second line but I'm not sure if that means it's legal or not. I know the standard says this:
    A pointer to a union object, suitably converted, points to each of its members
    The issue from the defective report still remains even after the pointer is "suitably converted" to an int or float pointer like in your code or my example above.
    Last edited by MOS-6581; 05-25-2014 at 12:44 AM.

  9. #39
    Registered User
    Join Date
    May 2014
    Posts
    121
    Ignoring the issue of unions for a moment, I'd like to bring up an example again that was posted on the first page in this thread:
    Code:
    void *mem = malloc(sizeof(int) + sizeof(float));
    int *a = mem;
    *a = 42;
    float *b = mem;
    *b = 42;
    I asked if this was an aliasing violation and grumpy said it was:
    Quote Originally Posted by grumpy View Post
    Yes, because of what is done with a and b (they are both aliasing the same thing) not because of the malloc() call.
    I know I originally wrote that I thought this violated the strict aliasing rule but I no longer believe that to be the case. Everyone here agree that it's allowed to change the effective type of an allocated object by modifying it so how can this be an aliasing violation when both of the accesses to the object modify it and nothing else? Reading b before setting the effective type to float would've been a strict aliasing violation but that's not what we're doing.

  10. #40
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    Have you stopped to think about the rationale behind the strict aliasing rule?

    Taken in isolation, the C and C++ standards are simply a list of rules which conforming compilers must follow, along with a non-exhaustive list of situations where the standard "has nothing to say." But WHY is it this way?

    What is aliasing? Really what we mean is referential aliasing, i.e. the existence of distinct referent objects which can access the same "real object" in memory. Why do we care? Because compilers must assume that, without prior knowledge, these referents may overlap. In order to produce code which is correct in all situations the compiler has to assume that nearly all referents are overlapping. This can lead to extremely poor performance of the compiled code.

    What do we do about it? Well, static analysis can help. In some situations where the compiler can see all the code at once, it might be able to prove the distinctness of the referents and thereby avoid many pointless memory loads and stores. But it cannot in general do this.

    But can we "cheat" in some way that boosts performance while still remaining correct? Well, it depends what we're willing to declare in the standard. Boiled down to the grossest resolution, what the strict aliasing rule does it permit the compiler to assume that referents which refer to different types of objects may never alias each other. We give up a little bit of flexibility by doing this -- we can no longer reliably play games like converting a floating point bit pattern directly to an integer -- but in return, the compiler gains enormous leverage to assume that many referents don't overlap, and aggressively optimize the compiled code.

    I bother to say this because knowing the rationale behind the rule helps you to better understand the (plausible) compiler decisions that go into applying it.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  11. #41
    Registered User
    Join Date
    May 2014
    Posts
    121
    The rationale behind the rule has always been clear to me, but it can be difficult to decipher how the rule will be applied. Judging by some of the defect reports I've linked to it seems that even the people writing the standard are a bit confused. Defect report #236 is a good example of this (it's still not resolved after all these years as far as I know): Defect Report #236

    Code:
    #include <stdlib.h>#include <stdio.h>
    
    
    void f(int *qi, double *qd) {
        int i = *qi + 2;
        *qd = 3.1;       /* Can the compiler move this assignment to the top of function? */
        *qd *= i;
        printf("qd = %f\n", *qd);
        return;
    }
    int main(void) {
        void *vp;
        int *pi;
        double *pd;
        vp = malloc(sizeof(int) + sizeof(double));
        pi = vp;
        *pi = 7;
        pd = vp;
        f(pi, pd);
        free(vp);
        return 0;
    }
    The committee responded with this:
    Both programs invoke undefined behavior, by calling function f with pointers qi and qd that have different types but designate the same region of storage. The translator has every right to rearrange accesses to *qi and *qd by the usual aliasing rules.
    There is nothing in the standard that forbids this since the aliasing rule is entirely defined in terms of how you access memory; not what kind of pointers you have to it. No wonder I'm confused when even the people who are writing the standard can't get it right. With that said, common sense should dictate that you don't write code like the one in this post.
    Last edited by MOS-6581; 05-28-2014 at 10:56 PM.

  12. #42
    Registered User MutantJohn's Avatar
    Join Date
    Feb 2013
    Posts
    2,665
    So out of curiousity, MOS, do you actually have any code to post attempting to solve your problem? I feel like this topic isn't going anywhere and your line :
    No wonder I'm confused when even the people who are writing the standard can't get it right.
    made me kind of think there's some arrogance and future posts aren't going to lead anywhere useful.

    Idk. You could be right and that's fantastic but enough is enough. Are you going to touch a keyboard and write code or just focus on this one part of the standard?

    I mean, I assumed it was a fairly common practice to allocate buffers and treat them like you would a simple stack. I've seen it before, allocate an array of some such bytes and then just treat it like a stack.

    I'm not sure if this violates aliasing rules (it probably does) but at a point in time, I think the only way to make a pool work is to violate the aliasing rules. I could be wrong but there's a point in time where it's just like, "Dude, write some code already."

    Remember, yolo programming best programming! And maybe the strict aliasing only applies if you attempt to dereference two different pointers at the same location.

    Like in my example, there's a void pointer to the buffer but I never dereference it. Maybe it's the act of dereferencing the pointer itself that violates the rules.

  13. #43
    Master Apprentice phantomotap's Avatar
    Join Date
    Jan 2008
    Posts
    5,108
    I'm not sure if this violates aliasing rules (it probably does) but at a point in time, I think the only way to make a pool work is to violate the aliasing rules.
    O_o

    That is not necessarily the case.

    As has been said numerous times by numerous people, where the memory lives is irrelevant.

    If you attempt to read/write the same memory location from incompatible pointers you are likely violating the "Strict Aliasing" rule.

    If your code really violates the strict aliasing rule, the "allocated buffer" is not your problem--at least only problem.

    Soma
    “Salem Was Wrong!” -- Pedant Necromancer
    “Four isn't random!” -- Gibbering Mouther

  14. #44
    Registered User
    Join Date
    May 2014
    Posts
    121
    Quote Originally Posted by MutantJohn View Post
    I'm not sure if this violates aliasing rules (it probably does) but at a point in time, I think the only way to make a pool work is to violate the aliasing rules.
    You're allowed to change the effective type of dynamically allocated memory so there won't be a problem unless you write one type and then try to read from an incompatible pointer. The key difference here that grumpy pointed out is that you're allowed to change the effective type of dynamically allocated memory by writing to it so the following should definitely be allowed according to the standard:
    Write type A
    Read type A
    Write type B
    Read type B

    The exception to this rule in C99 is unions and that has caused a lot of confusion even for those that write the compilers. The standard is still unclear about this but one of the defect reports made it clear that you're not allowed to change the effective type of unions through a pointer that isn't a union. That's why I believe Codeplug's example isn't correct.
    Code:
    union U {
        int x;
        float y;
    };
    
    void *mem = malloc(sizeof(U));
    union U *u = mem;
    float *p1 = mem;
    float *p2 = &mem.y;
    *p1 = 1.0f; /* Not allowed */
    *p2 = 2.0f; /* Not allowed */
    u->y = 3.0f; /* Allowed */

  15. #45
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    >> The standard is still unclear about this but one of the defect reports made it clear that you're not allowed to change the effective type of unions through a pointer that isn't a union.
    Are you talking about "Report #236" - which is closed and ends with:
    Committee Response

    Both programs invoke undefined behavior, ...
    Which is common sense to me - even though language lawyers aren't happy with the current wording of the standard.

    >> That's why I believe Codeplug's example isn't correct.
    Don't base your beliefs on an old, closed defect report that didn't go anywhere.
    6.5p6 of C99 still has the same wording that 236 tried to change. I don't see anything else in C99 that says the effective type of a union can not be changed via a pointer.

    I still believe my example is well defined.

    If being a language lawyer is something you enjoy:
    C99 revisited | Software is Crap

    gg

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Strict aliasing
    By MOS-6581 in forum C Programming
    Replies: 4
    Last Post: 05-17-2014, 05:18 PM
  2. enums and strict aliasing rule
    By cyberfish in forum C++ Programming
    Replies: 6
    Last Post: 08-10-2010, 08:44 PM
  3. How to know if my code is ANSI strict ?
    By jabka in forum C Programming
    Replies: 1
    Last Post: 10-19-2007, 07:32 AM
  4. Replies: 4
    Last Post: 08-27-2007, 11:51 PM
  5. Array of object & memory
    By richard_hooper in forum C# Programming
    Replies: 7
    Last Post: 05-21-2005, 01:08 AM