A 16-bit or 32-bit integer occupies two or four bytes, and CPUs disagree about which of those bytes comes first in memory. The byte order converter below shows both layouts for any value you enter, together with the result of htons() or htonl().
Byte order converter
- Result of the call
- Read back without ntoh*
- Binary
What endianness means
Endianness is the order in which a CPU stores the bytes of a multi-byte number. Take the 32-bit value 0x12345678. A big-endian machine puts 12 at the lowest address, then 34, 56, 78, in the order a person reads the hex digits. A little-endian machine stores 78 56 34 12, least significant byte first.
| Order | Bytes of 0x12345678, lowest address first | Typical CPUs |
|---|---|---|
| Big-endian | 12 34 56 78 | SPARC, IBM z/Architecture, network protocols |
| Little-endian | 78 56 34 12 | x86, x86-64, ARM and RISC-V in their usual modes |
Neither is better. The difference only shows when bytes leave one machine and are read by another, or when code looks at an integer through a char pointer.
Network byte order and the four functions
The Internet protocols fixed one order for header fields: big-endian, also called network byte order. Port numbers in TCP and UDP headers and addresses in IPv4 headers all travel that way, so a program on a little-endian CPU has to swap the bytes before writing them into a sockaddr. That is what these functions from arpa/inet.h do:
htons(): host to network, 16 bits (ports)htonl(): host to network, 32 bits (IPv4 addresses)ntohs()andntohl(): the reverse, for values read from the network
On a big-endian CPU all four return their argument unchanged. The program below prints the bytes before and after conversion, using the documentation address 192.0.2.1:
#include <arpa/inet.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
static void dump(const char *label, const void *p, size_t n)
{
const unsigned char *b = p;
printf("%-18s", label);
for (size_t i = 0; i < n; i++)
printf(" %02x", b[i]);
putchar('\n');
}
int main(void)
{
uint16_t port = 8080; /* 0x1f90 */
uint32_t addr = 0xc0000201; /* 192.0.2.1 */
uint16_t nport = htons(port);
uint32_t naddr = htonl(addr);
dump("8080 host:", &port, sizeof port);
dump("8080 htons:", &nport, sizeof nport);
dump("192.0.2.1 host:", &addr, sizeof addr);
dump("192.0.2.1 htonl:", &naddr, sizeof naddr);
printf("ntohs(htons(8080)) = %u\n", (unsigned)ntohs(nport));
printf("forgot htons: peer sees port %u\n", (unsigned)ntohs(port));
return 0;
}Output on x86-64:
8080 host: 90 1f
8080 htons: 1f 90
192.0.2.1 host: 01 02 00 c0
192.0.2.1 htonl: c0 00 02 01
ntohs(htons(8080)) = 8080
forgot htons: peer sees port 36895The classic bug: port 8080 becomes 36895
Fill a struct sockaddr_in by hand, write addr.sin_port = 8080; and the server binds without any error. It just listens on the wrong port. In memory the value is 90 1F; the kernel reads that field as network order, so it sees 0x901F, which is 36895 in decimal. The last line of the output above computes exactly that, and so does the byte order converter's "Read back without ntoh*" line for its default value.
The client usually makes the matching mistake and connects to 36895 as well, so everything works until a correct client, or a tool such as nc, tries port 8080 and gets "Connection refused". getaddrinfo() returns the port already converted, which is how the socket programming guide builds its server.

How to check your machine
The byte order converter assumes a little-endian host, which fits x86 and ARM in their usual modes. To confirm what a given machine does, lscpu from util-linux reports it directly:
$ lscpu | grep -i endian
Byte Order: Little EndianPython exposes the same fact in the sys module:
$ python3 -c "import sys; print(sys.byteorder)"
littleIn C, store 1 in a uint32_t and look at the byte at the lowest address. memcpy() into an unsigned char is the clean way to do it; a union works with gcc as well, but memcpy() avoids any argument about aliasing rules.
#include <stdint.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
uint32_t x = 1;
unsigned char first;
memcpy(&first, &x, 1); /* look at the byte at the lowest address */
puts(first == 1 ? "little-endian" : "big-endian");
return 0;
}$ gcc -Wall -Wextra -o endian endian.c && ./endian
little-endianFloats are not covered
htonl() takes an integer. Passing a float converts its value to an integer first, so 3.75 arrives as 3, and gcc 14 with -Wall -Wextra gave no warning. Copying the bits through memcpy() instead, as the FAQ below describes, relies on both machines using IEEE 754. Nearly every current CPU does, but the C standard does not promise it; the IEEE 754 page covers the bit layout.
A last question the converter cannot answer: what about a struct written to a socket with one send() call? Byte order is only half the problem there, because the compiler may also insert padding between fields, and two compilers need not agree on it.
