Very interesting ordering caveat with NSArray arrayWithObjects:

  • Some (or most) people might be aware of this caveat, but I was not, so
    I'll share it.

    Consider this code:

        NSArray *array = [NSArray arrayWithObjects:[MyCounterClass
    newObject], [MyCounterClass newObject], nil];

    where [MyCounterClass newObject] is a static method that returns a new
    autoreleased instance that simply stores an incrementing int32 counter
    in its instance variable, e.g.

        self.oid = SomeStaticCounter++;  // (or ideally,
    OSAtomicIncrement32Barrier(&SomeStaticCounter);

    Now, one would expect that the array would contain:

    element 1: MyCounterInstance.oid=1
    element 2: MyCounterInstance.oid=2

    However, this is NOT the case.  Either the compiler or the runtime
    executes the SECOND call to [MyCounterClass newObject] FIRST, so the
    array actually contains:

    element 1: MyCounterInstance.oid=2
    element 2: MyCounterInstance.oid=1

    NSArray arrayWithObjects: is of course correctly putting the objects
    into the array in the correct natural ordering, but the objects are
    CREATED on the stack in the oppose order.  Maybe most people knew
    that, I did not.  So the (or a) workaround is:

        MyCounterClass *object1 = [MyCounterClass newObject];
        MyCounterClass *object2 = [MyCounterClass newObject];
        NSArray *array = [NSArray arrayWithObjects: object1, object2, nil];

    - Eric
  • On Fri, Apr 3, 2009 at 8:30 PM, Eric Hermanson <zmonster...> wrote:
    > Some (or most) people might be aware of this caveat, but I was not, so I'll
    > share it.
    >
    > Consider this code:
    >
    >    NSArray *array = [NSArray arrayWithObjects:[MyCounterClass newObject],
    > [MyCounterClass newObject], nil];
    >
    > where [MyCounterClass newObject] is a static method that returns a new
    > autoreleased instance that simply stores an incrementing int32 counter in
    > its instance variable, e.g.
    >
    >    self.oid = SomeStaticCounter++;   // (or ideally,
    > OSAtomicIncrement32Barrier(&SomeStaticCounter);
    >
    > Now, one would expect that the array would contain:
    >
    >        element 1: MyCounterInstance.oid=1
    >        element 2: MyCounterInstance.oid=2
    >
    > However, this is NOT the case.  Either the compiler or the runtime executes
    > the SECOND call to [MyCounterClass newObject] FIRST, so the array actually
    > contains:
    >
    >        element 1: MyCounterInstance.oid=2
    >        element 2: MyCounterInstance.oid=1
    >
    > NSArray arrayWithObjects: is of course correctly putting the objects into
    > the array in the correct natural ordering, but the objects are CREATED on
    > the stack in the oppose order.  Maybe most people knew that, I did not.  So
    > the (or a) workaround is:
    >
    >    MyCounterClass *object1 = [MyCounterClass newObject];
    >    MyCounterClass *object2 = [MyCounterClass newObject];
    >    NSArray *array = [NSArray arrayWithObjects: object1, object2, nil];

    This is actually a "feature" of C, which ObjC inherits. C does not
    define an order of operations except across "sequence points", which
    are basically semicolons, although C defines some others too.
    Different parts of a statement are executed in an arbitrary order.
    Basically, the compiler can decide which order suits it best. As such,
    conforming C (and thus ObjC) code must never rely on the order of
    execution of function arguments, arithmetic subexpressions, or
    anything else of that nature. In any given statement, there should
    never be two parts of the statement that have interdependent side
    effects.

    Wikipedia has a decent discussion of this concept along with some
    illuminating examples:

    http://en.wikipedia.org/wiki/Sequence_point

    Mike
  • A comma is a sequence yet the order in arrayWithObjects is
    indeterminate.  It must be the var arg causing the ordering mix.

    Sent from my iPhone

    On Apr 4, 2009, at 12:29 AM, Michael Ash <michael.ash...> wrote:

    > On Fri, Apr 3, 2009 at 8:30 PM, Eric Hermanson <zmonster...>
    > wrote:
    >> Some (or most) people might be aware of this caveat, but I was not,
    >> so I'll
    >> share it.
    >>
    >> Consider this code:
    >>
    >> NSArray *array = [NSArray arrayWithObjects:[MyCounterClass
    >> newObject],
    >> [MyCounterClass newObject], nil];
    >>
    >> where [MyCounterClass newObject] is a static method that returns a
    >> new
    >> autoreleased instance that simply stores an incrementing int32
    >> counter in
    >> its instance variable, e.g.
    >>
    >> self.oid = SomeStaticCounter++;  // (or ideally,
    >> OSAtomicIncrement32Barrier(&SomeStaticCounter);
    >>
    >> Now, one would expect that the array would contain:
    >>
    >> element 1: MyCounterInstance.oid=1
    >> element 2: MyCounterInstance.oid=2
    >>
    >> However, this is NOT the case.  Either the compiler or the runtime
    >> executes
    >> the SECOND call to [MyCounterClass newObject] FIRST, so the array
    >> actually
    >> contains:
    >>
    >> element 1: MyCounterInstance.oid=2
    >> element 2: MyCounterInstance.oid=1
    >>
    >> NSArray arrayWithObjects: is of course correctly putting the
    >> objects into
    >> the array in the correct natural ordering, but the objects are
    >> CREATED on
    >> the stack in the oppose order.  Maybe most people knew that, I did
    >> not.  So
    >> the (or a) workaround is:
    >>
    >> MyCounterClass *object1 = [MyCounterClass newObject];
    >> MyCounterClass *object2 = [MyCounterClass newObject];
    >> NSArray *array = [NSArray arrayWithObjects: object1, object2,
    >> nil];
    >
    > This is actually a "feature" of C, which ObjC inherits. C does not
    > define an order of operations except across "sequence points", which
    > are basically semicolons, although C defines some others too.
    > Different parts of a statement are executed in an arbitrary order.
    > Basically, the compiler can decide which order suits it best. As such,
    > conforming C (and thus ObjC) code must never rely on the order of
    > execution of function arguments, arithmetic subexpressions, or
    > anything else of that nature. In any given statement, there should
    > never be two parts of the statement that have interdependent side
    > effects.
    >
    > Wikipedia has a decent discussion of this concept along with some
    > illuminating examples:
    >
    > http://en.wikipedia.org/wiki/Sequence_point
    >
    > Mike
  • Eric Hermanson wrote:

    > Some (or most) people might be aware of this caveat, but I was not, so
    > I'll share it.
    >
    > Consider this code:
    >
    > NSArray *array = [NSArray arrayWithObjects:[MyCounterClass
    > newObject], [MyCounterClass newObject], nil];
    >
    > where [MyCounterClass newObject] is a static method that returns a new
    > autoreleased instance that simply stores an incrementing int32 counter
    > in its instance variable, e.g.
    >
    > self.oid = SomeStaticCounter++;  // (or ideally,
    > OSAtomicIncrement32Barrier(&SomeStaticCounter);
    >
    > Now, one would expect that the array would contain:
    >
    > element 1: MyCounterInstance.oid=1
    > element 2: MyCounterInstance.oid=2
    >
    > However, this is NOT the case.  Either the compiler or the runtime
    > executes the SECOND call to [MyCounterClass newObject] FIRST, ....
    >
    > NSArray arrayWithObjects: is of course correctly putting the objects
    > into the array in the correct natural ordering, but the objects are
    > CREATED on the stack in the oppose order.  Maybe most people knew
    > that, I did not.  So the (or a) workaround is:
    >
    > MyCounterClass *object1 = [MyCounterClass newObject];
    > MyCounterClass *object2 = [MyCounterClass newObject];
    > NSArray *array = [NSArray arrayWithObjects: object1, object2,
    > nil];

    You've discovered the joy of implementation-defined behavior. The
    problem is not anything inherent in arrayWithObjects:; it's in the
    fact that you're modifying a variable (SomeStaticCounter) twice
    between a single pair of sequence points. The elements of an argument
    list are not guaranteed to be evaluated in any order. It could go
    front to back, or back to front, or alternate left and right from the
    outside in.

    Note, by the way, that the order in which the arguments are evaluated
    has nothing whatsoever to do with the order in which they're put on
    the stack. They are not *created* on the stack. They're all evaluated
    and then they're pushed in the proper sequence.
  • On Fri, Apr 3, 2009 at 8:30 PM, Eric Hermanson <zmonster...> wrote:

    [MyCounterClass newObject] is a static method that returns a new
    > autoreleased instance

    A method that begins with the word "new" is supposed to return an object
    that you own. See:

        <
    http://developer.apple.com/documentation/Cocoa/Conceptual/MemoryMgmt/Articl
    es/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-SW1

    >

    sherm--

    --
    Cocoa programming in Perl: http://camelbones.sourceforge.net
  • On Sat, Apr 4, 2009 at 2:14 AM, Eric Hermanson <zmonster...> wrote:
    > A comma is a sequence yet the order in arrayWithObjects is indeterminate.
    >  It must be the var arg causing the ordering mix.

    No, the comma *operator* is a sequence point. In other words, if you
    just write "foo(), bar();", then the order is defined. But when you
    call a function you are not using the comma operator. Yes, it is the
    same character, but it's in a different context. And there, the comma
    is not a sequence point, so the order of evaluation of the different
    function argument expressions is completely undefined. Nothing to do
    with varargs, you'll find the same thing happening with any function
    or method.

    The comp.lang.c FAQ has a question addressing exactly this:

    http://c-faq.com/expr/comma.html

    It says, "The comma operator does guarantee left-to-right evaluation,
    but the commas separating the arguments in a function call are not
    comma operators."

    I highly encourage everybody reading this mailing list to read through
    that entire FAQ. C has a lot of behaviors that are not exactly
    obvious, and forewarned is forearmed.

    Mike
  • This is not the var arg, it append with all functions (on Intel Mac).
    Evaluation order in argument passing is undefined.
      Someone in this thread (Mike IIRC) quoted the sentence in the C
    standard that states this.

    Le 4 avr. 09 à 08:14, Eric Hermanson a écrit :

    > A comma is a sequence yet the order in arrayWithObjects is
    > indeterminate.  It must be the var arg causing the ordering mix.
    >
    > Sent from my iPhone
    >
    > On Apr 4, 2009, at 12:29 AM, Michael Ash <michael.ash...>
    > wrote:
    >
    >> On Fri, Apr 3, 2009 at 8:30 PM, Eric Hermanson <zmonster...>
    >> wrote:
    >>> Some (or most) people might be aware of this caveat, but I was
    >>> not, so I'll
    >>> share it.
    >>>
    >>> Consider this code:
    >>>
    >>> NSArray *array = [NSArray arrayWithObjects:[MyCounterClass
    >>> newObject],
    >>> [MyCounterClass newObject], nil];
    >>>
    >>> where [MyCounterClass newObject] is a static method that returns a
    >>> new
    >>> autoreleased instance that simply stores an incrementing int32
    >>> counter in
    >>> its instance variable, e.g.
    >>>
    >>> self.oid = SomeStaticCounter++;  // (or ideally,
    >>> OSAtomicIncrement32Barrier(&SomeStaticCounter);
    >>>
    >>> Now, one would expect that the array would contain:
    >>>
    >>> element 1: MyCounterInstance.oid=1
    >>> element 2: MyCounterInstance.oid=2
    >>>
    >>> However, this is NOT the case.  Either the compiler or the runtime
    >>> executes
    >>> the SECOND call to [MyCounterClass newObject] FIRST, so the array
    >>> actually
    >>> contains:
    >>>
    >>> element 1: MyCounterInstance.oid=2
    >>> element 2: MyCounterInstance.oid=1
    >>>
    >>> NSArray arrayWithObjects: is of course correctly putting the
    >>> objects into
    >>> the array in the correct natural ordering, but the objects are
    >>> CREATED on
    >>> the stack in the oppose order.  Maybe most people knew that, I did
    >>> not.  So
    >>> the (or a) workaround is:
    >>>
    >>> MyCounterClass *object1 = [MyCounterClass newObject];
    >>> MyCounterClass *object2 = [MyCounterClass newObject];
    >>> NSArray *array = [NSArray arrayWithObjects: object1, object2,
    >>> nil];
    >>
    >> This is actually a "feature" of C, which ObjC inherits. C does not
    >> define an order of operations except across "sequence points", which
    >> are basically semicolons, although C defines some others too.
    >> Different parts of a statement are executed in an arbitrary order.
    >> Basically, the compiler can decide which order suits it best. As
    >> such,
    >> conforming C (and thus ObjC) code must never rely on the order of
    >> execution of function arguments, arithmetic subexpressions, or
    >> anything else of that nature. In any given statement, there should
    >> never be two parts of the statement that have interdependent side
    >> effects.
    >>
    >> Wikipedia has a decent discussion of this concept along with some
    >> illuminating examples:
    >>
    >> http://en.wikipedia.org/wiki/Sequence_point
    >>
    >> Mike

    >
  • On 04.04.2009, at 02:30, Eric Hermanson wrote:
    > Now, one would expect that the array would contain:
    >
    > element 1: MyCounterInstance.oid=1
    > element 2: MyCounterInstance.oid=2
    >
    > However, this is NOT the case.  Either the compiler or the runtime
    > executes the SECOND call to [MyCounterClass newObject] FIRST, so the
    > array actually contains:
    >
    > element 1: MyCounterInstance.oid=2
    > element 2: MyCounterInstance.oid=1

    Nothing to do with ObjC or NSArray. That's a part of the C standard
    left open to allow platform-specific optimizations. I've blogged about
    it in a simpler context here:

    http://zathras.de/blog-c-parameter-evaluation-order.htm

    Cheers,
    -- Uli Kusterer
    "The Witnesses of TeachText are everywhere..."
    http://www.zathras.de
previous month april 2009 next month
MTWTFSS
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30      
Go to today