Of all the various reasons to define address lengths and wire formats (that'll get burned into billions of dollars of ASICs over decades), "C doesn't have a default type for it" is frankly the worst.
Which is a byte array. When you pass this to a function, it does not fit in a single register. The equality operator doesn't work, nor do other logical operators. Everything has to be defined. 64 bit would have been so easy. And it would still have been enough.
Edit: somebody replied and deleted their comment. Since I had written a response I am editing this comment.
> Then your real complaint... is that you don't have 128-bit machine registers
Agreed, although my real complaint is more towards IPv6, for not thinking about this real-life limitation.
> what the urgent need to pass IPv6 addresses in machine registers
It's much slower than IPv4 to deal with! 64 bit addresses wouldn't have had this problem and still would have enough space for everyone on earth.
> And how is it "irritating af"?
Apart from the speed concerns:
> The equality operator doesn't work, nor do other logical operators. Everything has to be defined.
So what? Didn't you notice that the 128 bits are split in half: {network|host}? Anything that needs to route things around is only concerned about the first 64 bits. Once you're hitting on the local scope the last 64 bits is what matters ~99.9% of the time. So you can have your efficient 64-bit comparison right there for most of the traffic.
> Everything has to be defined. 64 bit would have been so easy. And it would still have been enough
I agree. It sucks, 64 bits almost certainly would have worked and languages/libraries/OSes tend to not do their fair share at taking care of IPv6-related stuff. That, along with all the prettyprinting/parsing business with the colons is legitimately annoying.
However, IPv6 was designed not just to fix IPv4's sins, but to ensure that there'd never be an address exhaustion issue with IPv6, no matter how many decades it would be in use. That's why there's 128 bits of overkill; the IPv6 designers knew that the IPv4->IPv6 transition would be awful (they even underestimated just how awful it'd be), that IPv6 would keep running for a long time once it got deployed -- and wanted to do their best to avoid subjecting the world to a transition from IPv6.
> When you pass this to a function, it does not fit in a single register.
Depends on the architecture. On amd64 (the architecture I'm typing this comment on), a 128-bit struct, like defined above, will fit in registers — yes, in 64-bit registers. The amd64 ABI[1] will split them across two registers. For example, if we take the above struct and use it,
movabsq $578437695752307201, %rdi
xorl %esi, %esi
jmp bar
The nasty looking number is the initialization; it's easier to see in hex:
In [1]: hex(578437695752307201)
Out[1]: '0x807060504030201'
That ends up in %rdi, the first of the amd64 arg-passing registers. The next half is zeros, so it goes in %rsi (the compiler just zeros it). Then we "call" bar. (There is tail-call optimization here)
The non-optimized version is similar, but more drawn out:
# for some reason we zero it first
movq $0, -16(%rbp)
movq $0, -8(%rbp)
# then we init it again… okay gcc.
movb $1, -16(%rbp)
movb $2, -15(%rbp)
movb $3, -14(%rbp)
movb $4, -13(%rbp)
movb $5, -12(%rbp)
movb $6, -11(%rbp)
movb $7, -10(%rbp)
movb $8, -9(%rbp)
# move the struct into rdx/rax
movq -16(%rbp), %rdx
movq -8(%rbp), %rax
# just to move it into the arg-passing registers
movq %rdx, %rdi
movq %rax, %rsi
# call the function
call bar
> It's much slower than IPv4 to deal with! 64 bit addresses wouldn't have had this problem and still would have enough space for everyone on earth.
It's twice as wide, yes, but that doesn't necessarily mean you're needing to pass it in memory.
> still would have enough space for everyone on earth.
IP addresses aren't just numbers assigned to machines. They must also — effectively — encode the route to that machine; you can sort of imagine it as a tree, with each bit giving you successively more local directions on the Internet. Now, of course, that's not exactly how it works, but the point is that some parts of that tree are going to be sparsely populated. Having a wider address space means you can make bigger, but not necessarily full, allocations in the address space, allowing you to keep whole networks together in a common prefixes, which results in simpler routing tables. (At least, that's the idea as I understand it.)
And it's not just 1 per person: I have more than one device, and I move between networks during the course of a day. If all of the networks I frequented actually had IPv6, I'd bog down at least 6–8 IPv6 addresses during the course of a boring day, and that's not counting stuff like networking equipment.
It's not just a simple question of "are there enough bits to assign each person an address"
> The equality operator doesn't work, nor do other logical operators.
On amd64 at least, __int128_t exists, I believe, and I think it should come with operators. In higher-level languages, you can define < and == simply enough.
Now, I grant that the above is highly architecture specific, and there definitely exist architectures where this doesn't apply. And those, yes, 128-bit will be slower than a theoretical 64-bit. I just don't think it's worth worrying about.
> IP addresses aren't just numbers assigned to machines. They must also — effectively — encode the route to that machine; you can sort of imagine it as a tree, with each bit giving you successively more local directions on the Internet. Now, of course, that's not exactly how it works, but the point is that some parts of that tree are going to be sparsely populated. Having a wider address space means you can make bigger, but not necessarily full, allocations in the address space, allowing you to keep whole networks together in a common prefixes, which results in simpler routing tables. (At least, that's the idea as I understand it.)
Exactly, and the huge address space allows the standard to right out set some structure to it, with 48 bits for global routing, 16 bits for subnet addressing, plus 64 bits for link-local, interface identifier. Routing is only concerned about the first 64 bits (which has all the fast operations you could dream of) and global routing only the first 48 even. Obviously doing some work to match the topology to the address space in a smart way by leveraging CIDR to reduce the size of routing tables is always going to be useful.