Operating System Thinking Chapter 5 More Bits and Bytes


Chapter 5 More Bits and Bytes

Author: Allen B. Downey

Original: Chapter 5 More bits and bytes

Translator: Feilong

Protocol: CC BY-NC-SA 4.0

Representation of 5.1 Integers

You may know that computers represent integers in binary. For positive numbers, binary representation is very straightforward. For example, decimal 5 is represented as binary0b101

For negative numbers, the clearest representation uses symbolic bits to indicate whether a number is positive or negative. But there is another expression, called two’s complement, which is more common because it works better with hardware.

In order to find a positive complement,-xNeed to findxThe binary representation reverses all bits and then adds 1. For example, to represent decimal-5Start with decimal 5. If you write it in 8-bit form, it’s0b0000 0101。 Reverse all bits and get them.0b1111 1011

In complements, the leftmost bit corresponds to the symbol bit. It’s zero in the positive and 1 in the negative.

To convert an 8-bit value to a 16-bit value, we need to add more zeros to the positive and more 1 to the negative. In fact, we need to copy symbol bits to new bits, a process called “symbol expansion”.

In C, unless you use itunsignedDeclare them, and all integer types are signed (able to represent positive and negative numbers). The difference between them, and the reason why this declaration is so important, is that operations on unsigned integers do not use symbolic extensions.

5.2 bitwise operation

People who learn C sometimes do bitwise arithmetic.&and|Feeling confused. These operators treat integers as vectors of bits and perform logical operations on the corresponding bits.

For example,&Perform the “and” operation. If both operands are 1, the result is 1, otherwise 0. Here’s an execution on two four-digit values&Examples of operations:

& 1010

In C, this means expressions12 & 10The value is 8.

Similarly,|Perform the OR operation if at least one of the two operands is 1, otherwise 0.

| 1010

So the expression12 | 10The value is 14.

Last,^Operators perform the XOR operation. If one of the two operands is 1, not all of them are 1, the result is 1.

^ 1010

So the expression12 ^ 10The value is 6.

Usually,&Used to clear some bits in the bit vector.|For setting bits,^Used for inversion. Here are some details:

Clearance bit:For anyxx & 0The value is 0.x & 1The value isx。 So if you do a vector and 3 and you do it, it only keeps the two right-most bits, and the rest of the bits are set to zero.

& 0011

In this context, 3 is called a “mask” because it chooses some bits and masks the rest.

Settings:Similarly, for anyxx | 0The value isxx | 1The value is 1. So if you do or operate on a vector with 3, it will set two bits on the right, and the rest will remain the same.

| 0011

Inversion:Finally, if you do exclusive or operation on a vector with 3, it reverses two bits to the right and the rest remains unchanged. As an exercise, see if you can use it.^The complement of 12 is calculated. Tip: – 1 What does the complement mean?

C language also provides shift operators.<<and>>It can move bits left or right. Each move to the left doubles the value, so5 << 110,5 << 220. Moving one bit to the right halves the value (rounding it down), so5 >> 12,2 >> 11.

5.3 Floating Point Representation

Floating-point numbers are represented in the binary form of scientific counting. In decimal form, larger numbers are written in the form of multiplying coefficients by the exponents of the decimal system. For example, the speed of light is about 1.2.998 * 10 ** 8Meters per second.

Most computers use the IEEE standard to perform floating point arithmetic. C LanguagefloatTypes usually correspond to 32-bit IEEE standards, whiledoubleUsually it corresponds to 64-bit standard.

Of the 32-bit criteria, the left-most one is the symbol bit.s。 The next eight digits are exponentsqThe last 23 bits are the coefficients.c。 Floating point values are:

(-1) ** s * c * 2 ** q

This is almost true, but with one exception. Floating-point numbers are usually normalized, so there is a number in front of the decimal point. For example, in decimal system, we usually use2.998 * 10 ** 8Instead of2998 * 10 ** 5Or any other equivalent representation. In binary, a normalized floating point usually has a number 1 before the binary decimal point. Since the number in this position is always 1, we can remove it from the representation to save space.

For example, decimal 13 is represented as0b1101In floating point numbers, it is1.011 * 2 ** 3。 So the index is 3 and the coefficient is 101 (plus 20 zeros).

This is almost true, but the index is stored as “offset”. In the 32-bit standard, the offset is 127, so index 3 should be stored at 130.

To package and unpack floating-point numbers in C, we can use unions and bitwise operations. Here is an example:

union {
    float f;
    unsigned int u;
} p;

p.f = -13.0;
unsigned int sign = (p.u >> 31) & 1;
unsigned int exp = (p.u >> 23) & 0xff;

unsigned int coef_mask = (1 << 23) - 1;
unsigned int coef = p.u & coef_mask;

printf("%d\n", sign);
printf("%d\n", exp);
printf("0x%x\n", coef);

This code is in the warehouse of this book.float.cMedium.

The consortium allows us to use itp.fStore floating-point numbers and use them laterp.uRead as an unsigned integer.

To get the symbol bit, we need to move it to the right by 31 bits, and then use a 1-bit mask to select the rightmost bit.

To get the index, we need to move it 23 bits to the right and then select the rightmost 8 bits (hexadecimal value).0xffContains 8 1).

In order to obtain the coefficients, we need to decompress the 23 bits on the rightmost side and ignore the remaining bits, by constructing a mask with the 23 bits on the right side being 1 and the rest being 0. The simplest way is to move 1 left by 23 bits and then subtract 1.

The output of the program is as follows:


As expected, the sign bit of the negative number is 1. The exponent is 130, which includes the offset. And the coefficient is 101 with 20 zeros. I print it out in hexadecimal.

As an exercise, try to assemble or decomposedoubleIt uses the 64-bit standard. See IEEE Floating Point Wikipedia.

5.4 Consortium and Memory Errors

The C complex has two common uses. One is the binary representation for accessing data, as you saw in the previous section. Another is to store different forms of data. For example, you can use a consortium to represent a numerical value that may be an integer, a floating point, a complex or a rational number.

However, federations are error-prone, depending entirely on you, as a programmer, to track the data types in the federation. If you write a floating point number and read it as an integer, the result is usually meaningless.

In fact, if you read somewhere in memory by mistake, the same thing happens. One possible way is to read across the end of the array.

I’ll start with this function to see what happens. This function allocates an array on the stack and populates it with 0 to 99.

void f1() {
    int i;
    int array[100];

    for (i=0; i<100; i++) {
        array[i] = i;

Next, I define a function that creates small arrays and deliberately accesses elements before and after the beginning:

void f2() {
    int x = 17;
    int array[10];
    int y = 123;

    printf("%d\n", array[-2]);
    printf("%d\n", array[-1]);
    printf("%d\n", array[10]);
    printf("%d\n", array[11]);

If I call it oncef1andf2The results are as follows:


The details here depend on the compiler, which arranges variables on the stack. From these results, we can infer that the compiler willxandyPlace them together and under the array (low address). When we read across the boundaries of the array, it seems that we have the data left on the stack by the last function call.

In this example, all variables are integers, so it’s easier to understand the principle. But usually when you cross the boundaries of an array, you may read any kind of value. For example, if I modifyf1To create a floating-point array, the result is:


The last two values are the results of interpreting floating-point numbers as integers. If you encounter this output during debugging, it’s hard to figure out what happened.

Representation of 5.5 Strings

Strings sometimes have related problems. First, remember that C’s string ends with empty characters. When you allocate space for strings, don’t forget the extra bytes at the end.

Also, remember that the letters and numbers in the C string are encoded as ASCII codes. The ASCII codes of numbers 0-9 are 48-57, not 0-9. ASCII code 0 isNULCharacter, used to mark the end of a string. ASCII codes 1-9 are special characters for some communication protocols. ASCII code 7 is a ringing bell. In some terminals, printing them makes a sound.

'A'The ASCII code is 65.'a'It’s 97. Here’s their binary form:

65 = b0100 0001
97 = b0110 0001

Careful readers will find that there is only one difference between them. This rule applies to all other characters. From the sixth digit of the right to play the role of “case” bits, 0 for capital letters, 1 for lowercase letters.

As an exercise, write a function that receives strings and converts lower-case characters into upper-case letters by reversing the sixth bit. As a challenge, you can make it faster by reading 32 or 64 bits of a string at a time instead of one character. If the length of the string is a multiple of 4 or 8 bytes, this optimization will be easier to implement.

If you read through the end of the string, you may see strange characters. Conversely, if you create a string and then unintentionally read it as an integer or floating point, the result is difficult to interpret.

For example, if you run:

char array[] = "allen";
float *p = array;
printf("%f\n", *p);

You will find that the ASCII representation of the first eight characters of my name can be interpreted as a double-precision floating point, which is 69779713878800585457664.