如何在 C/C++ 中减去两个 IPv6 地址(128 位数字)?

How can I subtract two IPv6 addresses (128bit numbers) in C/C++?

我将 IP 地址存储在 sockaddr_in6 中,它支持四个 32 位的数组,addr[4]。本质上是一个 128 位数。

我正在尝试计算给定 IPv6 范围内的 IP 数量(中间有多少 IP)。所以这是一个使用两个长度为四的数组从另一个中减去一个的问题。

问题是因为没有128bit数据类型,我不能转换成十进制。

非常感谢!

您可以使用某种 big-int 库(如果您可以容忍 LGPL,则可以选择 GMP)。幸运的是,如有必要,128 位减法很容易手动模拟。下面是计算 (a-b) 绝对值的快速演示,对于 128 位值:

#include <iostream>
#include <iomanip>

struct U128
{
    unsigned long long hi;
    unsigned long long lo;
};

bool subtract(U128& a, U128 b)
{
    unsigned long long carry = b.lo > a.lo;
    a.lo -= b.lo;
    unsigned long long carry2 = b.hi > a.hi || a.hi == b.hi && carry;
    a.hi -= carry;
    a.hi -= b.hi;
    return carry2 != 0;
}

int main()
{
    U128 ipAddressA = { 45345, 345345 };
    U128 ipAddressB = { 45345, 345346 };

    bool carry = subtract(ipAddressA, ipAddressB);

    // Carry being set means that we underflowed; that ipAddressB was > ipAddressA.
    // Lets just compute 0 - ipAddressA as a means to calculate the negation 
    // (0-x) of our current value. This gives us the absolute value of the
    // difference.
    if (carry)
    {
        ipAddressB = ipAddressA;
        ipAddressA = { 0, 0 };
        subtract(ipAddressA, ipAddressB);
    }

    // Print gigantic hex string of the 128-bit value
    std::cout.fill ('0');
    std::cout << std::hex << std::setw(16) << ipAddressA.hi << std::setw(16) << ipAddressA.lo << std::endl; 
}

这为您提供了差异的绝对值。如果范围不是很大(64 位或更少),那么 ipAddressA.lo 可以作为简单的 unsigned long long.

如果您有性能方面的顾虑,您可以利用编译器内部函数来利用某些架构,例如 amd64,如果您希望它在该处理器上是最佳的。 _subborrow_u64 是用于必要减法工作的 amd64 内在函数。

in6_addr 结构以网络字节顺序存储地址 - 或 'big endian' - 最高有效字节 @ s6_addr[0]。您不能指望其他工会成员的命名或定义一致。即使您通过(不可移植的)uint32_t 字段访问联合,这些值也必须使用 ntohl 进行转换。因此,便携式 查找差异的方法需要一些工作。

您可以将 in6_addr 转换为 uint64_t[2]。坚持典型的 'bignum' 约定,我们使用 [0] 表示低 64 位,[1] 表示高 64 位:

static inline void
in6_to_u64 (uint64_t dst[2], const struct in6_addr *src)
{
    uint64_t hi = 0, lo = 0;

    for (unsigned int i = 0; i < 8; i++)
    {
        hi = (hi << 8) | src->s6_addr[i];
        lo = (lo << 8) | src->s6_addr[i + 8];
    }

    dst[0] = lo, dst[1] = hi;
}

和区别:

static inline unsigned int
u64_diff (uint64_t d[2], const uint64_t x[2], const uint64_t y[2])
{
    unsigned int b = 0, bi;

    for (unsigned int i = 0; i < 2; i++)
    {
        uint64_t di, xi, yi, tmp;

        xi = x[i], yi = y[i];
        tmp = xi - yi;
        di = tmp - b, bi = tmp > xi;
        d[i] = di, b = bi | (di > tmp);
    }

    return b; /* borrow flag = (x < y) */
}