tags:

views:

117

answers:

3

so the following converts big endians to little ones

uint32_t ntoh32(uint32_t v)
{
    return (v << 24)
        | ((v & 0x0000ff00) << 8)
        | ((v & 0x00ff0000) >> 8)
        | (v >> 24);
}

works. like a charm.

I read 4 bytes from a big endian file into char v[4] and pass it into the above function as

 ntoh32 (* reinterpret_cast<uint32_t *> (v))

that doesn't work - because my compiler (VS 2005) automatically converts the big endian char[4] into a little endian uint32_t when I do the cast.

AFAIK, this automatic conversion will not be portable, so I use

uint32_t ntoh_4b(char v[])
{
    uint32_t a = 0;
    a |= (unsigned char)v[0];
    a <<= 8;
    a |= (unsigned char)v[1];
    a <<= 8;
    a |= (unsigned char)v[2];
    a <<= 8;
    a |= (unsigned char)v[3];
    return a;
}

yes the (unsigned char) is necessary. yes it is dog slow.

there must be a better way. anyone ?

+1  A: 

The better way, IMHO, is using the htonl and ntohl functions. If you want to be really portable you can not think in terms of "convert to little endian". Rather you should think about "convert to host endian". That's what ntohl is for, if your input is a big-endian for sure (which is what the network standard is).

Now, if you read your bytes individually, you can read them as an unsigned long (in binary mode) - this should give you a big-endian long, and then you can convert it to whatever you need - if you need host endian, then ntohl.

Eli Bendersky
perhaps you did not understand my question. the method implementation is not the problem - the method *signature* is.
KnickerKicker
@KnickerKicker: consider my second paragraph - either read the bytes directly into a long, or read them into char[4] which is part of a union with a long
Eli Bendersky
@Eli: I'll try that - it should work. thanks :)anyway, do you know why the compiler does that, and do all compilers do it
KnickerKicker
@KnickerKicker: I'm not sure it does what you think it does, by the way. Keep in mind that the compiler has no way of knowing what endianness your char array is
Eli Bendersky
A: 

Dog slow? Did you actually measure it? You can rewrite it in the style of ntoh32 and significantly reduce the number of operations:

uint32_t ntoh_4b(char v[])
{
    return ( (uint32_t)(unsigned char)v[0] << 24 )
         | ( (uint32_t)(unsigned char)v[1] << 16 )
         | ( (uint32_t)(unsigned char)v[2] <<  8 )
         | ( (uint32_t)(unsigned char)v[3]       );
}
Secure
well, I just assumed it'll be :)I'll try to profile 'em later on - but I do think it'll be slower due to all the array indexing... - anyways, nice *reinterpretion*
KnickerKicker
It's a char array (sizeof==1) with constant indices. I'll bet it's lightning fast, even without compiler optimization, but in fact I would not care until it proves to be a bottleneck. Your version has a temporary variable with 8 write and 8 read accesses, completely missing in my version. That I meant with the operations.
Secure
@Secure: see the benchmarks below
KnickerKicker
A: 

(Posting this as a separate answer to preserve indentation)

The test rig...

    union {
        char a[4];
        uint32_t i;
    } t;
    t.i = 0xaabbccdd;

    uint32_t v;
    for (uint32_t i = 0; i < -1; ++i)
    {
        //v = ntohl (t.i); (1)
        //v = ntoh32 (t.i); (2)
        //v = ntoh_4b (t.a);  (3)
    }

the disassembly of ntoh32...

movl    %edi, -4(%rbp)
movl    -4(%rbp), %eax
movl    %eax, %edx
sall    $24, %edx
movl    -4(%rbp), %eax
andl    $65280, %eax
sall    $8, %eax
orl %eax, %edx
movl    -4(%rbp), %eax
andl    $16711680, %eax
shrl    $8, %eax
orl %eax, %edx
movl    -4(%rbp), %eax
shrl    $24, %eax
orl %edx, %eax
leave

the disassembly of ntoh_4b ...

movq    %rdi, -8(%rbp)
movq    -8(%rbp), %rax
movzbl  (%rax), %eax
movzbl  %al, %eax
movl    %eax, %edx
sall    $24, %edx
movq    -8(%rbp), %rax
addq    $1, %rax
movzbl  (%rax), %eax
movzbl  %al, %eax
sall    $16, %eax
orl %eax, %edx
movq    -8(%rbp), %rax
addq    $2, %rax
movzbl  (%rax), %eax
movzbl  %al, %eax
sall    $8, %eax
orl %eax, %edx
movq    -8(%rbp), %rax
addq    $3, %rax
movzbl  (%rax), %eax
movzbl  %al, %eax
orl %edx, %eax
leave

And finally, the results. I've included the time for the C library's ntohl to provide a baseline for comparision

//v = ntohl (t.i); (1)
real    0m35.030s
user    0m34.739s
sys 0m0.245s   

//v = ntoh32 (t.i); (2)
real    0m36.272s
user    0m36.070s
sys 0m0.115s

//v = ntoh_4b (t.a);  (3)
real    0m40.162s
user    0m40.013s
sys 0m0.097s
KnickerKicker