Endian Reversal?

T

Tosha

What is the fastest way to convert __int64 type from BigEndian To Little
Endian?
I wrote this function:
---
uint64 EndianReverse(uint64 number) {
typedef unsigned __int8 uint8;
const int size = sizeof(uint64);
uint8 polje[size] = {0};
uint8 *ptr = reinterpret_cast<uint8*>(&number);
int index = 0;
for (int i=size-1 ; i>=0 ; i--) {
polje[index] = *(ptr+i);
index++;
}
uint64 *retPtr = reinterpret_cast<uint64*>(polje);
return *retPtr;
}
 
V

Victor Bazarov

Tosha said:
What is the fastest way to convert __int64 type from BigEndian To Little
Endian?
I wrote this function:
---
uint64 EndianReverse(uint64 number) {
typedef unsigned __int8 uint8;
const int size = sizeof(uint64);
uint8 polje[size] = {0};
uint8 *ptr = reinterpret_cast<uint8*>(&number);
int index = 0;
for (int i=size-1 ; i>=0 ; i--) {
polje[index] = *(ptr+i);
index++;
}
uint64 *retPtr = reinterpret_cast<uint64*>(polje);
return *retPtr;
}

The "fastness" has to be _measured_, not deduced from the types of
operations used. That said, I'd expect this to be a bit faster:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e; ++b, --e)
std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

.... and it is (just measured), by about 20%, on my machine.

V
 
F

Fabio Fracassi

The "fastness" has to be _measured_, not deduced from the types of
operations used. That said, I'd expect this to be a bit faster:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e; ++b, --e)
std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?

template<class T> void InplaceEndianReverse(T& t)
{
unsigned char* tp = reinterpret_cast<unsigned char *>(&t);
for (unsigned char *b = tp, *e = tp + sizeof(T) - 1; b < e; ++b, --e)
std::swap(*b, *e);
return;
}

Or saving at least one of the memcpy's in the out of place syntax?

template<class T> T EndianReverse(T t)
{
T result=t; // "hides" a memcpy.
unsigned char* tp = reinterpret_cast<unsigned char *>(&result);
for (unsigned char *b = tp, *e = tp + sizeof(T) - 1; b < e; ++b, --e)
std::swap(*b, *e);
return result;
}

Shouldn't this make a difference, especially if it is inlined?

Fabio
 
V

Victor Bazarov

Fabio said:
The "fastness" has to be _measured_, not deduced from the types of
operations used. That said, I'd expect this to be a bit faster:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e;
++b, --e) std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?

Because the behaviour in _your_ case is undefined. You're *not*
allowed to use the pointer obtained from 'reiterpret_cast' for
anything _except_ converting back to what it was.
template<class T> void InplaceEndianReverse(T& t)
{
unsigned char* tp = reinterpret_cast<unsigned char *>(&t);
for (unsigned char *b = tp, *e = tp + sizeof(T) - 1; b < e; ++b,
--e) std::swap(*b, *e);
return;
}

Or saving at least one of the memcpy's in the out of place syntax?

template<class T> T EndianReverse(T t)
{
T result=t; // "hides" a memcpy.
unsigned char* tp = reinterpret_cast<unsigned char *>(&result);
for (unsigned char *b = tp, *e = tp + sizeof(T) - 1; b < e; ++b,
--e) std::swap(*b, *e);
return result;
}

Shouldn't this make a difference, especially if it is inlined?

Oh yes, I'll be it does, but in 'comp.lang.c++' it's customary to
suggest only _proper_ solutions, whose behaviour is well-defined
according to the International Standard of the C++ language. Both
your examples have undefined behaviour.

V
 
F

Fabio Fracassi

Victor said:
Fabio said:
Victor said:
template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e;
++b, --e) std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?

Because the behaviour in _your_ case is undefined. You're *not*
allowed to use the pointer obtained from 'reiterpret_cast' for
anything _except_ converting back to what it was.

And what exactly is the point in converting it back and forth, without doing
anything in between?
Besides, the call to memcpy does two implicit casts, one from (T*) to
(void*) and one from (void*) to (unsigned char*), which is what allows you
to interpret the data as a series of bytes. Effectively your doing an
implicit cast from (T*) to (unsigned char*) with the memcpy. AFAIK,
reinterpret_cast does the same, albeit without the copying.
template<class T> void InplaceEndianReverse(T& t)
{
unsigned char* tp = reinterpret_cast<unsigned char *>(&t);
for (unsigned char *b = tp, *e = tp + sizeof(T) - 1; b < e; ++b,
--e) std::swap(*b, *e);
return;
}
[sniped one simmilar example]
Shouldn't this make a difference, especially if it is inlined?

Oh yes, I'll be it does, but in 'comp.lang.c++' it's customary to
suggest only _proper_ solutions, whose behaviour is well-defined
according to the International Standard of the C++ language. Both
your examples have undefined behaviour.

I think my example's have exactly the same behaviour as yours. We both let
the compiler interpret a typed pointer as another typed pointer. This might
be implementational defined behaviour, but I don't see a way around this.

Fabio
 
A

AnalogFile

Fabio said:
Victor said:
Fabio said:
Victor Bazarov wrote:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e;
++b, --e) std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?
Because the behaviour in _your_ case is undefined. You're *not*
allowed to use the pointer obtained from 'reiterpret_cast' for
anything _except_ converting back to what it was.

And what exactly is the point in converting it back and forth, without doing
anything in between?

If there's no point, then do not do it.
Besides, the call to memcpy does two implicit casts, one from (T*) to
(void*) and one from (void*) to (unsigned char*),

No. The call converts to void* only.
Anything else happens inside the implementation of memcpy.
And memcpy is an implementation problem.

Sure, most implementations will do what you say. But it is not guaranteed.
to interpret the data as a series of bytes. Effectively your doing an
implicit cast from (T*) to (unsigned char*) with the memcpy. AFAIK,
reinterpret_cast does the same, albeit without the copying.

That is what happens on most implementations, and on most
implementations your code actually does work.

Still there's nothing in the language definition that makes it
guaranteed. If I write a compiler that will do things differently, it
will still be a perfectly legal compiler.
I think my example's have exactly the same behaviour as yours. We both let
the compiler interpret a typed pointer as another typed pointer. This might
be implementational defined behaviour, but I don't see a way around this.

No. That is what your code tries to do. And is not legal.
His code converts to void* and calls memcpy. What happens inside memcpy
is that memory content is copyed. Without any type cast. The cast inside
memcpy is that of a magic spell.
 
F

Fabio Fracassi

AnalogFile said:
Fabio said:
Victor said:
Fabio Fracassi wrote:
Victor Bazarov wrote:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e;
++b, --e) std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?
Because the behaviour in _your_ case is undefined. You're *not*
allowed to use the pointer obtained from 'reiterpret_cast' for
anything _except_ converting back to what it was.

And what exactly is the point in converting it back and forth, without
doing anything in between?

If there's no point, then do not do it.

What is the point of having it in the language then? And how could I solve
such problems without relying on undefined behaviour?
No. The call converts to void* only.
Anything else happens inside the implementation of memcpy.

I don't think so. memcpy doesn't know anything about types, and does no
casting whatsoever. Its signature is "void* memcpy(void* dest, const void*
src, size_t n);". So if someone uses a typed pointer in memcpy's dest
parameter there is an implicit cast from void* to the type which was used.

As long as the cast doesn't change the bit pattern of the pointed to values
this operation is save.

Fabio
 
A

AnalogFile

Fabio said:
AnalogFile said:
Fabio said:
Victor Bazarov wrote:

Fabio Fracassi wrote:
Victor Bazarov wrote:

template<class T> T EndianReverse(T t)
{
unsigned char uc[sizeof t];
memcpy(uc, &t, sizeof t);
for (unsigned char *b = uc, *e = uc + sizeof(T) - 1; b < e;
++b, --e) std::swap(*b, *e);
memcpy(&t, uc, sizeof t);
return t;
}

Why not do it inplace?
Because the behaviour in _your_ case is undefined. You're *not*
allowed to use the pointer obtained from 'reiterpret_cast' for
anything _except_ converting back to what it was.
And what exactly is the point in converting it back and forth, without
doing anything in between?
If there's no point, then do not do it.


What is the point of having it in the language then?

I wrote "if" there's no point. It's in the language for those cases
where there actually is a point.
And how could I solve
such problems without relying on undefined behaviour?

Use the system library.
I don't think so. memcpy doesn't know anything about types, and does no
casting whatsoever.

memcpy is part of the system and what it does or does not you cannot
say, except for the documented side effect of copying underlying bytes
from source to destination.
Its signature is "void* memcpy(void* dest, const void*
casting whatsoever. Its signature is "void* memcpy(void* dest, const void*
src, size_t n);". So if someone uses a typed pointer in memcpy's dest
parameter there is an implicit cast from void* to the type which was used.

sorry. I do not understand the above.
 
F

Fabio Fracassi

AnalogFile said:
Fabio Fracassi wrote:
memcpy is part of the system and what it does or does not you cannot
say, except for the documented side effect of copying underlying bytes
from source to destination.


sorry. I do not understand the above.

I'm just saying that memcpy is not resposible for the reinterpreting of the
pointer types, but that it happens implicitly at the function call, i.e

T1* t1p;
T2* t2p;
memcpy (t2p, t1p, sizeof(T1));

is equivalent to:

T1* t1p;
T2* t2p;

void * v1p = (void*) t1p;
void * v2p = (void*) t2p;
memcpy (v2p, v1p, sizeof(T1));

After writeing this I see the subtle difference between this way an what I
have written, because in this case the "backward" cast from (void*) to
(T2*) never happens, because t2p already holds the correct address.

Now does the standard really not guarantee that the pointed to memory is
left alone when I do an reinterpret_cast? Could you point me the right
section where I could look this up?

I'm not trying to be argumentative, but I can't belive that something as
basic as an inplace endianess reversal can't be done without resorting to
undefined behaviour or system specific libraries. If it is I consider this
a Defect.

Fabio
 
A

AnalogFile

Fabio said:
I'm just saying that memcpy is not resposible for the reinterpreting of the
pointer types, but that it happens implicitly at the function call, i.e

T1* t1p;
T2* t2p;
memcpy (t2p, t1p, sizeof(T1));

is equivalent to:

T1* t1p;
T2* t2p;

void * v1p = (void*) t1p;
void * v2p = (void*) t2p;
memcpy (v2p, v1p, sizeof(T1));

After writeing this I see the subtle difference between this way an what I
have written, because in this case the "backward" cast from (void*) to
(T2*) never happens, because t2p already holds the correct address.

Neither of the above sequences ever does any backward cast from void*
They in fact are perfectly equivalent.
Now does the standard really not guarantee that the pointed to memory is
left alone when I do an reinterpret_cast? Could you point me the right
section where I could look this up?

I'm again not sure I understand you.
If I get it right then, the "pointed to" memory does not change. Not at
the time of the cast.
I'm not trying to be argumentative, but I can't belive that something as
basic as an inplace endianess reversal can't be done without resorting to
undefined behaviour or system specific libraries. If it is I consider this
a Defect.

If you think hard at it you realize that the very concept of "endianness
reversal" is totally system dependent.

You are assuming that the system does have some sort of endianness to
begin with. Like a 4 bytes value is either stored as 1234 or 4321. And
probably you also assume values to be 2s complement. But as far as the
language is concerned it may be 1423 and not be 2s complement!
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,133
Latest member
MDACVReview
Top