Hexadecimal constant expressions hosed (and other probs)
| From: | Matthew H. North | Date: | Wed, 26 Apr 2000 16:35:57 +0000 |
| Subject: | Hexadecimal constant expressions hosed (and other probs) | ||
| Groups: | php.version4 | ||
| Request: | Send a blank email to php-version4+get-14106@lists.php.net to get a copy of this message | ||
Try something like this:
$var1 = 0x7FFFFFFF;
$var2 = 0xFFFFFFFF;
$var3 = 0x8FFFFFFF;
$var4 = 0x9FFFFFFF;
$var5 = 0x8FFFFFF1;
$var6 = 0x80000001;
$var7 = $var1 + 0x7FFFFFFF;
$var8 = (0x08FFFFFF << 4);
$var9 = intval("AFFFFFFF", 16);
echo $var1; echo "\n";
echo $var2; echo "\n";
echo $var3; echo "\n";
echo $var4; echo "\n";
echo $var5; echo "\n";
echo $var6; echo "\n";
echo $var7; echo "\n";
echo $var8; echo "\n";
echo $var9; echo "\n";
Results:
2147483647
0
0
0
0
0
4294967294
-1879048208
2147483647
("\n" is separated so we don't accidentally cause the interpreter to convert
the vars to strings before printing them)
var1 prints out (correctly) 2147483647
var2 prints out 0, leading me to believe that PHP deals with integers as
signed 32-bit, by default (even though FFFFFFFF should be -1, not 0).
However, if that's the case, var4 through var6 should print out some version
of a negative number. Instead they print out 0 as well.
Then, looking at var7, we see that you *can* assign and print a value
greater than 7FFFFFFF in unsigned mode, *if* you add to it to increment the
value to that size, as opposed to directly assigned it using the '0x'
operator. However, var8 shows that shifting left doesn't afford the same
'convenience'.
Using intval doesn't help either. As you can see from var9, not only does
intval not translate AFFFFFFF to the proper unsigned 32 bit integer, it
doesn't even translate it to the proper signed 32 bit integer! In fact,
it's as if the 32nd bit is forcibly turned off - *any* hex number beyond
7FFFFFFF is returned by intval as 2147483647.
Lastly, I haven't figured out exactly under what circumstances this occurs,
but frequently when I'm working with large 32 bit integers, the Apache
process running my PHP script core dumps.
These ambiguities, oddities, and downright bugs, make it extremely hard to
work with 32 bit integers in any kind of reliable way. This is very
disappointing. IP addresses are 32 bit integers. If one wishes to work
with IP addresses at the bit level, one needs to be able to convert a
dotted-quad notated IP address into a straight 32 bit integer. For those
that may not understand this, here's why:
Suppose you have a 25 bit network subnet beginning at 192.91.199.128, the
subnet mask for a 25 bit subnet being 255.255.255.128.
192.91.199.128 as an integer is 3227240320, which in binary is
11000000010110111100011110000000
255.255.255.128 as in integer is 4294967168, which in binary is
11111111111111111111111110000000
Now, applying the bitwise AND operation to the subnet mask and a connecting
IP address, we can determine whether or not that IP address is in this
network subnet. If the result is equal to the network subnet, the
connecting IP address is within it. If it is not equal to the network
subnet, the IP address is not within it.
Suppose we get a connection from 192.91.199.129:
11000000010110111100011110000001
& 11111111111111111111111110000000
-----------------------------------
11000000010110111100011110000000
Which is the same as the network subnet, so this IP address is within the
subnet.
Suppose we get a connection from 192.91.199.127:
11000000010110111100011101111111
& 11111111111111111111111110000000
-----------------------------------
11000000010110111100011100000000
Which is not the same as the network subnet (but 25 is off), so this IP
address is not within the subnet.
These kind of operations are extremely common in systems that deal with IP
addresses regularly (routers for example). In my case, I have a set of
subnets with their corresponding subnet masks. I want to be able to do
something like this:
$localnets = array ( "192.91.199.128/25",
"192.91.55.64/26" );
foreach ($localnets as $net) {
list ($addr, $bits) = explode("/", $net);
// convert the dotted quad network address into a 32 bit integer
list ($a, $b, $c, $d) = explode(".", $addr);
$numaddr = ($a << 24) + ($b << 16) + ($c << 8) + $d;
// convert the dotted quad remote address into a 32 bit integer
list ($a, $b, $c, $d) = explode(".", $REMOTE_ADDR);
$rnumaddr = ($a << 24) + ($b << 16) + ($c << 8) + $d;
$mask = bits2mask($bits);
echo ((($rnumaddr & $mask) == $numaddr) ? "Local" : "Remote") .
"\n";
}
function bits2mask ($bits) {
$mask = 0xFFFFFFFF;
$mask <<= $bits; // This should put 32 - $bits zeros at the end of
// $mask, thus giving me the proper subnet mask.
// Instead, since the assignment of $mask to
// 0xFFFFFFFF yields 0, the algorithm is hosed.
// To work around this, I can do:
$mask = 0x7FFFFFFF + 0x7FFFFFFF + 1; // achieves 0xFFFFFFFF
$mask <<= $bits; // but this causes a core dump.
return $mask;
}
I'm contemplating looking at the PHP code that causes all these bugs - to
fix it, and for my own amusement ;-)
- Matthew H. North
Programmer/Engineering
CTSnet Internet Services
Tel: (858) 637-3600
Fax: (858) 637-3630
mailto:ctsmhn@cts.com