16

I ran the following program on little-endian [LE] machine [Linux, Intel processor]. I am unable to explain the 3 outputs in below code snippet. Since machine is LE, the value of a is stored as 0x78563412. When printing, it is displaying its actual value. Since its an LE machine, I expect ntohl() to be a no-op and display 0x78563412, which it is doing. However, I expect 0x12345678 for 2nd print statement containing htonl(). Can someone please help me understand why they are same?

int main() 
{
    int a = 0x12345678; 

    printf("Original - 0x%x\n", (a)); 
    printf("Network - 0x%x\n", htonl(a)); 
    printf("Host - 0x%x\n", ntohl(a)); 

    return 0;
}

Output:

Original - 0x12345678
Network - 0x78563412
Host - 0x78563412
Bhaskar
  • 2,549
  • 1
  • 21
  • 23

4 Answers4

26

Since its an LE machine, I expect ntohl() to be a no-op

That's the mistake. Network byte order is big-endian, host byte order is little-endian. Therefore, both ntohl and htonl return a byte-swapped version of their input.

Remember, the point of htonl is that you can take an integer on the host, then write:

int i = htonl(a);

and the result is that the memory of i, when interpreted using network byte order, has the same value that a does. Hence, if you write the object representation of i to a socket and the reader at the other end expects a 4-byte integer in network byte order, it will read the value of a.

and display 0x78563412

Is this what you intended to write? If ntohl were a no-op (or rather, an identity function), then your third line necessarily would print the same thing as your first line, because you would have ntohl(a) == a. This is what happens on big-endian implementations, where your program prints:

Original - 0x12345678
Network - 0x12345678
Host - 0x12345678
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • As @Alok mentioned below, I was expecting the following behavior will always hold true: `x == htonl(ntohl(x))`. But this is not happening and your explanation was very helpful. – Bhaskar Jul 11 '12 at 18:51
  • 2
    @Bhaskar: Brian Roach's point is important too, then: you never calculated `htonl(ntohl(a))`. You calculated `htonl(a)` and `ntohl(a)`. – Steve Jessop Jul 12 '12 at 08:08
  • 1
    [demo for big-endian machines](https://godbolt.org/g/3Z6CG7). Compared with the little-endian version on the same architecture it can be seen that no byte swap instructions are needed – phuclv Aug 07 '18 at 06:06
19

htonl and ntohl are exactly the same functions. They are supposed to satisfy htonl(ntohl(x)) == x. They are named differently only for documentation (you make it explicit that you are converting from host-to-network or the other way, even if it's the same). So, on a little-endian machine, they both perform byte-swapping, and on a big-endian machine, they are both no-ops.

Alok Singhal
  • 93,253
  • 21
  • 125
  • 158
  • 11
    On a hypothetical "stupid-endian" C implementation where the bytes are neither big-endian (ordered `4321`) nor little-endian (ordered `1234`), but ordered for example `3214`, you would still have `htonl(ntohl(x))`, but `htonl` and `ntohl` would not do the same thing, they'd be 8-bit rotations in opposite directions. I hope that no such architecture exists, but it could implement the sockets API thanks to the fact that `htonl` and `ntohl` are separate functions. – Steve Jessop Jul 10 '12 at 23:21
  • 2
    @SteveJessop you are right of course. This is probably the *real* reason we have two different functions. http://en.wikipedia.org/wiki/Endianness#Middle-endian – Alok Singhal Jul 10 '12 at 23:27
  • @SteveJessop Yeah, I just figured that out and was trying to delete my comment, but you're fast! :-). – Alok Singhal Jul 10 '12 at 23:31
  • @SteveJessop: I thought that was called VAX-endian? – SamB Jul 28 '12 at 00:26
  • @SamB VAX uses little endian, only VAX floating-point is different. Anyway [mixed endian](https://en.wikipedia.org/wiki/Endianness#Middle-endian) writes `1234` as `2143` and not `3214` – phuclv Aug 06 '18 at 02:03
6

Because you're passing a by value and therefore it isn't changed by either of those functions.

You're printing what htonl() and ntohl() are returning.

Edit to add: I missed where you thought one would be a no-op. It's not. Both are going to do the exact same thing on a LE machine; reverse the byte ordering. ntohl() is expecting you to be passing it a network byte ordered int

Brian Roach
  • 76,169
  • 12
  • 136
  • 161
0

In your program when you write int a; you know that a contains an host ordered integer, the program doesn't know that. You could as easily provide an int already containing a value in network order. Of course if you use any arithmetic operator on a value that is not in host order, the result will be incorrect from a network point of view if network order is not the same as host order.

But this is not so far fetched, the network ordered values are often kept exactly that way in low level structures, before being sent or just after being received.

What is wrong with your program is that when you call ntohl() your are promising to the ntohl() function that the int you are providing is some value stored in memory in network order. That's the contract. If it's not true, the function won't perform what you expect, and that is what you are seing.

As other explained on most systems (big or little but not stupid-endians) the two functions are usually identical, either a bytes reverse or a no-op.

kriss
  • 23,497
  • 17
  • 97
  • 116