Project Home
Project Home
Documents
Documents
Wiki
Wiki
Discussion Forums
Discussions
Project Information
Project Info
Forum Topic - memcpy( ) oddness on ARM: (7 Items)
   
memcpy( ) oddness on ARM  
Hi.  I'm new here, so this may be the wrong question in the wrong place. "Please advise."

I'm posting on behalf of a colleague who is too busy to do so.

He has a piece of C++ code in which he memcpy( ) 's one struct to another.  Pseudocode looks like this:

     void* myFunc( struct_type_one* pOne, struct_type_two* pTwo)
     {
          // ...

          memcpy(&pOne, &pTwo, sizeof(struct_type_two));
          // ...
      }

The strange thing is that he's copying to-and-from the ADDRESSES of the POINTERS, which in this case "ought to be an 
error," and yet the code "does the right thing" -- and, moreover, operates identically even when he REMOVES the '&' from
 the first two arguments of the memcpy( ).

The only way(s) I can see for this to be possible are 1) there are multiple, overloaded implementations of memcpy( ) in 
the system, or 2) the QNX implementation of either memcpy( ) or the compiler is "somehow" smart enough to detect pointer
-to-pointer arguments, notice that the size argument isn't size-of-a-pointer, and "correct" the code at compile time.

Out of curiosity, we've looked at the source code in memcpy.c -- and were really surprised at what we see there.  I 
gather the ARM processor can't load-and-store other than on ... is it 16-bit or 32-bit? ... boundaries, but...  it is 
not at all clear what is going on in the implementation.  Also, I've never seen the 'do {' part of a do-loop fall INSIDE
 the first case of a switch statement; not only would I never have thought to do that, but I'm astounded that it 
compiles -- and a little unsure of what's going to happen if the switch argument ISN'T 0.  Does the do loop get iterated
?  Strictly speaking, cases other than 0 "won't see the 'do {' part" of the loop, but exactly what's going to happen 
would almost have to depend on the precise details of how the compiler is going to IMPLEMENT a switch statement in 
machine code.

Can somebody shed some light on these curiosities?

Chris
Re: memcpy( ) oddness on ARM  
Hi Chris,

the do-while loop interlaced into a switch-case statement is known as "Duff's device" and is in fact a kind of 
optimizing loop-unrolling. Since the different 'case' labels are just that (i.e., labels), there is no problem with 
putting the 'do {' where it is... except, maybe, that it isn't obvious to read. 

If you think of the 'switch' as a multi-target 'goto', it becomes clear: 'switch'ing is done once, on the initial 
remainder modulo n (where n is the number of cases). Since case labels are arranged counting downwards, and all cases 
fall through (there's no break!), you'll be at a remainder of 0 at the end of the switch{} block and can now loop in 
multiples of n without checking the loop condition every time.

As for why copying from/to pointer addresses (instead of their pointee's addresses) copies the structures --- I'm 
clueless. 

- Thomas
Re: memcpy( ) oddness on ARM  
There are indeed multiple versions of memcpy() available depending on
exactly what you're doing (although that doesn't really explain the
behaviour which you are seeing).  Stock gcc will automatically use
__builtin_memcpy() vice std::memcpy() unless you've turned that off
somehow. I don't think our qcc version of the toolchain automatically
redirects that to the custom QNX libc versions, but I may be wrong about
that; easily checked though.  If you're not explicitly qualifying with
std, you might well be getting something other than what you're
expecting although, again that's easily checked.

As to why you're getting the behaviour which you are reporting, I have
no idea.  It certainly isn't correct if you're doing exactly what you
describe.  Note however that gcc usually permits &ary and &fn and
implements them as you might expect; perhaps that's what's actually
happening?  Strictly speaking they are not meaningful constructs -- I'd
have to drag out the standard to check if they're well formed or not --
since the name of an array or function is already it's address.

(The odd switch/do construct is probably the one usually called "Duff's
Device", which you can Google.  It is entirely legal, and very useful --
much to the surprise of almost everyone who sees it, so you're not
alone.  I'm not certain which of our several implementations you're
looking at, so I won't comment on any of your specific observations.)
Re: memcpy( ) oddness on ARM  
Thanks to both who've replied.  Sorry I haven't replied sooner.  I'm pretty busy and don't get on here often.  Plus I 
access this board with my home/personal, rather than work, email.  Not sure why I did that!

But I digress.

Anyway -- thanks for pointing me to Duff's Device.  I'll look that up.  Very interesting.

Not sure what you mean by the compiler allowing "&ary" and "&fn" constructs.  What do you mean by this terminology?

Chris
Re: memcpy( ) oddness on ARM  
Hi Chris,

what &ary and &fn mean becomes obvious at the end of the same paragraph - they mean "address of array" and "address of 
function", respectively.

But in your pseudocode, the addresses were taken from actual pointer variables (function arguments at that) -- no array 
anywhere in that context. Are you sure this is /exactly/ what the original code looked like?


Another comment, regarding Duff's device: My apologies to Mr. Duff. I miscited him. His original device wasn't intended 
to do a memcpy() (for which there are many much faster implementations on a lot of platforms), but instead to send a 
stream of bytes from a buffer out through one single port. So his device incremented only the source-, but not the 
destination pointer.

@Neil: I took a few minutes to dig through the standard. Two chapters seem to apply (see below). Specifically, I believe
 the following can be concluded:

1) Given an array a,  a  alone will evaluate to a pointer to the first element of a, i.e., should be equivalent to &a[0]
. 

2) Given an array a,  &a  will evaluate to a pointer to an object of the array type, with  a  being that object. 

In other words, while a and &a will be equal pointer values, you will notice a difference when you look at *a compared 
to *&a, or at sizeof *a and sizeof *&a.

3) Given a function f, writing &f is perfectly legal, and both should evaluate to identical types and values. 


Cheers,
- Thomas


Snippets from the C standard:

6.3.2.1 Lvalues, arrays, and function designators

1 An lvalue is an expression with an object type or an incomplete type other than void ... .  When an object is said to 
have a particular type, the type is specified by the lvalue used to designate the object. 
...

3 Except when it is the operand of the sizeof operator or the unary & operator, or is a string literal used to 
initialize an array, an expression that has type ‘‘array of type’’ is converted to an expression with type ‘‘
pointer to type’’ that points to the initial element of the array object and is not an lvalue. ...

4 A function designator is an expression that has function type. Except when it is the operand of the sizeof operator or
 the unary & operator, a function designator with type ‘‘function returning type’’ is converted to an expression 
that has type ‘‘pointer to function returning type’’.
...

6.5.3.2 Address and indirection operators

Constraints
1 The operand of the unary & operator shall be either a function designator, ... or an lvalue that designates an object 
... .
...

Semantics
3 The unary & operator yields the address of its operand. If the operand has type ‘‘type’’, the result has type ‘‘
pointer to type’’. 
... the result is a pointer to the object or function designated by its operand.
Re: memcpy( ) oddness on ARM  
Hi Thomas, 

> what &ary and &fn mean becomes obvious at the end of the same paragraph - they
>  mean "address of array" and "address of function", respectively.

Going back and looking at that again, it is clear to me only now that you've explained it.  I.e. it makes sense when 
read while already knowing what it means.  It didn't, so much, when I didn't already know.

> But in your pseudocode, the addresses were taken from actual pointer variables
>  (function arguments at that) -- no array anywhere in that context. Are you 
> sure this is /exactly/ what the original code looked like?

As much as I would like to give an ironclad guarantee, I can't.  The code wasn't mine but a colleague's, and I only saw 
it with my own eyes ONCE.  Anything's possible.

> @Neil: I took a few minutes to dig through the standard. Two chapters seem to 
> apply (see below). Specifically, I believe the following can be concluded:
> 
> 1) Given an array a,  a  alone will evaluate to a pointer to the first element
>  of a, i.e., should be equivalent to &a[0]. 
> 
> 2) Given an array a,  &a  will evaluate to a pointer to an object of the array
>  type, with  a  being that object. 
> 
> In other words, while a and &a will be equal pointer values, you will notice a
>  difference when you look at *a compared to *&a, or at sizeof *a and sizeof *&
> a.

This is a very subtle difference, one which I had not heretofore appreciated -- and one which I still don't quite know 
what to do with.  (Aack. Ending a sentence with a preposition.  Ptui.)  It would never occur to me to write "&a" if a 
were an array, so I have no actual experience in the semantics resulting from doing so.  If I did it accidentally I 
probably wouldn't notice, because of "a" and "&a" being "equal pointer values" and probably doing what I wanted, but if 
I tried to pass one of them to a function which wanted the other, and the compiler complained, I might simply never be 
able to find the problem.  At best I'd have to post the code someplace like this forum and hope someone else could spot 
something; at worst I'd have to redesign the module and recode from scratch to avoid having to pass that particular 
argument to that particular function.

> 3) Given a function f, writing &f is perfectly legal, and both should evaluate
>  to identical types and values. 

I am very familiar with this case.  However, that familiarity with the fact that these are "identical types and values,"
 would in fact specifically mislead me into expecting the same semantics from a and &a above.

> Snippets from the C standard:

I decline to comment further on the grounds that it might make me look idiotic.

Thanks for your help.

Chris
Re: memcpy( ) oddness on ARM  
[ Sorry to come back to this late: just got back from vacation. ]

On Wed, 2010-03-17 at 16:41 -0400, Thomas Haupt wrote:
> Another comment, regarding Duff's device: My apologies to Mr. Duff. I
> miscited him. His original device wasn't intended to do a memcpy()
> (for which there are many much faster implementations on a lot of
> platforms), but instead to send a stream of bytes from a buffer out
> through one single port. So his device incremented only the source-,
> but not the destination pointer.

If memory serves, the VAX compiler managed to turn his whole device into
a single instruction :-)

> @Neil: I took a few minutes to dig through the standard. Two chapters
> seem to apply (see below). Specifically, I believe the following can
> be concluded:

Thanks very much for checking up on that.  I can never trust my memory
any more and I wasn't sure if those were standard behaviours or gcc
extensions.

One very minor detail to anyone who might be doing future thread
archaeology is to remember that C will "decay" array objects into
pointers to those objects at the drop of a hat in most contexts.  It's a
little like quantum physics that way: it can actually be quite hard to
catch a real array object in the act....

Regards,
Neil