CIDR按位操作 - 我可以更聪明一点吗?

Dav*_*dom 18 php binary bit-manipulation cidr

我正在构建一个代表IPv4子网的类.我将网络地址和子网掩码存储为4字节二进制字符串,它们是在构造函数期间根据参数构建的.我希望构造函数接受的表示之一CIDR表示法.

我的按位操作有点生疏,我遇到的问题是将子网掩码的十进制整数CIDR表示转换为4字节的二进制字符串,反之亦然.我还发现我无法对字符串执行左/右移位 - 我确信之前已经成功完成了?


我已设法转换为二进制字符串以使用以下代码:

// An example input value.
$mask = 24; // 255.255.255.0

if ($mask < 0 || $mask > 32) {
  // Invalid prefix size
  throw new RangeException('Invalid CIDR prefix size');
} else if ($mask === 0) {
  // Handle 0
  $mask = "\x00\x00\x00\x00";
} else {
  // Left-pad a 4-byte string with $mask set bits
  $mask = pack('N', (0x01 << 31) >> ($mask - 1));
}
Run Code Online (Sandbox Code Playgroud)

我不喜欢这种逻辑有两个原因:

  • 我不喜欢把它0视为特例
  • 我不喜欢右移然后是左移

我确信有一种方法能够以一种0正确处理的方式更有效地完成这项工作而不将其视为一种特殊情况.


将二进制字符串转换回CIDR前缀大小的十进制表示时,我当前正在使用下面的代码.在验证以其他格式提供的子网掩码时,我有另一个非常相似的代码块,以确保设置位是连续的.

// An example input value.
$mask = "\xff\xff\xff\x00"; // /24

// Convert the binary string to an int so bit shifts will work
$mask = current(unpack('N', $mask));

// A counter to represent the CIDR
$cidr = 0;

// Loop and check each bit
for ($i = 31; $i > 0; $i--) {
  if (($mask >> $i) & 0x01) {
    $cidr++;
  } else {
    break;
  }
}

// Return the result
return $cidr;
Run Code Online (Sandbox Code Playgroud)

我不喜欢这个因为循环 - 我确信有一种更聪明的按位方式来做到这一点.


有更聪明的方法来完成这些任务吗?

想法/建议/一般滥用请...


编辑:

任何解决方案都需要在PHP 4.3.10及更高版本上运行,并且必须在32位和64位平台上运行.请记住,PHP中的所有整数都是有符号的,并且在32位平台上,任何内容都>= 0x80000000将被存储为double(因此对于按位操作不会很好).

Nik*_*kiC 8

你的第二个问题可以看作是在倒数中找到第一个设置位(而不是在非反转数中找到第一个未设置位),这相当于找到数字的整数log2.

这在按位世界中是一个相当普遍的问题,并且有许多速度优化算法.您正在使用(慢)明显的算法:http://www-graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

但我认为你并不真正关心速度,而是关于简洁,在这种情况下你可以这样做:

$cidr = (int) (32 - log(~current(unpack('N', $mask)) & 0xffffffff, 2));
Run Code Online (Sandbox Code Playgroud)

& 0xffffffff要与64个整数兼容是必要的.


Ja͢*_*͢ck 6

第二个问题可以通过文本方法解决:

$mask = "\xff\xff\xff\x00";

$cidr = strspn(sprintf('%b', current(unpack('N', $mask))), 1);
Run Code Online (Sandbox Code Playgroud)

它用于sprintf()将整数转换为二进制文本表示并strspn()计算初始值的数量.

更新

在64位计算机上,二进制文本表示用左边填充32个零,因此代码需要ltrim()像这样修补:

$cidr = strspn(ltrim(sprintf('%b', current(unpack('N', $mask))), 0), 1);
Run Code Online (Sandbox Code Playgroud)

更新2

第一个问题也可以通过文本方法解决,虽然需要使用str_split()(在PHP 4.x中不起作用):

$mask = vsprintf('%c%c%c%c', array_map('bindec', str_split(str_pad(str_repeat(1, $mask), 32, 0), 8)));
Run Code Online (Sandbox Code Playgroud)

更新3

对我有用的是以下(在32位和64位上测试):

$mask = pack('N', 0xffffffff << (32 - $mask));
Run Code Online (Sandbox Code Playgroud)

在此过程中,数字变为浮点数但保留足够的精度来处理位移.