Write in front
Before the postgraduate entrance examination, the book of CSAPP had been brushed 5 or 6 times, so I had a good understanding of book knowledge. When I was learning C + + recently, I brushed the famous CSAPP experiment.
0. Environmental preparation
It is best to prepare a pure Linux system. It is recommended to use docker to build a CentOS or Ubuntu system
Download of experimental data
CS:APP3e, Bryant and O’Hallaron
Please refer to the following article for environment construction on docker
CSAPP: lab0 – carrying environment
Pullcentos
system
docker pull centos
Establish directory mount to realize file synchronization
docker container run it v /Users/xxxx/yourFilePath:/csapp name=csapp_env centos /bin/bash
/Users / XXXX / yourfilepath please replace it with the directory you want to synchronize
: / CSAPP please also replace it with the directory you want to name
The CSAPP directory here is the directory synchronized with your local directory
After synchronization, you can find that the CSAPP directory under docker is synchronized with our yourfilepath file
A result similar to the above indicates that the configuration is correct
 Configuring the compilation environment
 Update Yum source
yum y update
 Install sudo
yum install sudo
 Installing the C / C + + compilation environment
yum install make automake gcc gccc++ kerneldevel
 Installing GDB
yum install gdb
 Prepare 32bit embedded C library
yum install glibcdevel.i686
 Read readme to complete the configuration
Here, you need to first enter the mapped file directory CSAPP file, and then refer to the readme file
shell To compile and run the btest program, type:
unix> make btest
unix> ./btest [optional cmd line args]
After completing the above operations, our configuration is complete.
Next, we can write our code in a native compiler. Then compile and run our code on the virtual container in docer. ✅
Write in compiler
Compile and run in the docker container
The blue arrow is compiled. The red arrow indicates operation
Note: the btest should be recompiled every time the bits. C file is changed. If you need to check the correctness of a single function, you can use the – f flag:
text $ ./btest f bitXor
DLC program can detect whether we have violations. If there is no output, there is no problem
text $ ./bits.c
Note that every time you close docker, you need to start our CentOS first in the next run.
First find us namedcsapp_env
Container ID of the container

Then the docker start container ID starts our container

Enter the following command to enter the running container
Docker exec  it container ID / bin / Bash
1. Start of experiment
* IMPORTANT. TO AVOID GRADING SURPRISES:
* 1. Use the dlc compiler to check that your solutions conform
* to the coding rules.
* 2. Use the BDD checker to formally verify that your solutions produce
* the correct answers.
*/
1.1 bitXor
a^b=
1.(ab)&(~a~b)
2.~(~a&~b)&~(a&b)
3.(a&~b)(~a&b)
The XOR operation can be expressed in these three ways. The specific derivation can be performed by myself. Referring to discrete mathematics, I pushed it myself and found that it is not difficult
Mainly the application of De Morgan law
\begin{align} A\bigoplus B & = \overline{\overline AB\cup A \overline B} \\ & = \overline{(A \cup \overline B)\cap (\overline A \cup B}) \\ & = ( \overline{(A \cup \overline B)\cap \overline A )\cup ( (A \cup \overline B)\cap B )}\\ & = \overline{(\overline A \overline B) \cup( A B)} \\ & =\overline{(\overline A \overline B)}\cap \overline{( A B)}\\ \end{align}
\]
We can skip this example by selecting the second operation
int bitXor(int x, int y) {
return ~(~x&~y)&~(x&y);
}
1.2 tmin
int tmin(void) {
return 1<<31;
}
1.3 tmax
Title Description
* isTmax  returns 1 if x is the maximum, two's complement number,
* and 0 otherwise
* Legal ops: ! ~ & ^  +
* Max ops: 10
* Rating: 1
*/
We consider the four digit maximumx=0111
Then x + 1 becomes1000
We are right1000
Take non0111
It will change back to the x value
If we can use equal here, is it done directly, but not equal? Here we can use a bit operation trick. We know that we and ourselves will get 0, that is, we can use exclusive or to judge equal!((~(x+1)^x))
Judge whether this is 1 to judge whether it is the maximum value
There is an exception herex=1
because1=1111
He uses the above formula to judge that it is also consistent, so he wants to make a special judgment 1!!(x+1)
This operation – 1 is not the same as the maximum value
int isTmax(int x) {
return !((~(x+1)^x))&!!(x+1);
}
1.4 allOddBits
/*
* allOddBits  return 1 if all oddnumbered bits in word set to 1
* where bits are numbered from 0 (least significant) to 31 (most significant)
* Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
* Legal ops: ! ~ & ^  + << >>
* Max ops: 12
* Rating: 2
*/
thinking
A=1010
A is a typical number with even bits of 1. It only needs a four bit binary numberX & A = A
This indicates that the binary is qualified. That’s just judgmentx & 0xAAAAAAAA == 0xAAAAAAAA
It’s OK, because it can’t be defined directly0xAAAAAAAA
We need some bit operation tips
int a=0xAA<<8; //0xAA00
int c=a0xAA; //0xAAAA
int d=c<<16c; //0xAAAAAAAA
The operation of the equal sign can be used directlya == b
Equivalent to!((a & b)^b)
int allOddBits(int x) {
int a=0xAA<<8;
int c=a0xAA;
int d=c<<16c;
return !((x&d)^(d));
}
1.5 negate
/*
* negate  return x
* Example: negate(1) = 1.
* Legal ops: ! ~ & ^  + << >>
* Max ops: 5
* Rating: 2
*/
thinking
A + ~A = 1
andA + neg A =0
Using these two formulas, we can getneg A = ~A + 1
int negate(int x)
return ~x+1 ;
}
1.6 isAsciiDigit
* isAsciiDigit  return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
* Example: isAsciiDigit(0x35) = 1.
* isAsciiDigit(0x3a) = 0.
* isAsciiDigit(0x05) = 0.
* Legal ops: ! ~ & ^  + << >>
* Max ops: 15
* Rating: 3
*/
thinking
Let’s take a look first0x39 and 0x30
Bit level representation of
00111001
and00110000
First of all, we must meetx>>4==3
Then I used some tips to satisfy the question that the last four bits are between 0 and 9
x & 0xF
The last four digits of X are saved Judge the range of the last four digits by whether – A is a negative number
c=~0xA+1
Implementation – A  Judge that negative numbers are and
0x8000
And is a positive number
int isAsciiDigit(int x) {
int a=!(x >> 4 ^0x3);
int b=x&0xF;
int c=~0xA+1;
int e=0x80<<4;
int d=!!((b+c)&(e));
return a&d ;
}
1.7 conditional
/*
* conditional  same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^  + << >>
* Max ops: 16
* Rating: 3
*/
thinking
x > 0 return y else return z
We need to find a way whenx != 0
When does x become0xFFFFFFFF
int a=!!(x^0x0); //a=0 if x=0 else a =1
int b=~a+1;
int c=~(y&~b)+1;
int d=~(z&b)+1;
return y+z+c+d
We finally return this way. The meaning of the above code is actually very simple
Ifx!=0
Then C will be equal to – y, and we can finally return Z, otherwise we will return y
1.8 isLessOrEqual
* isLessOrEqual  if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^  + << >>
* Max ops: 24
* Rating: 3
*/
thinking
Pay attention to direct usexy
It may explodeint
Therefore, such a simple judgment cannot be made
int a=x>>31&0x1;
int b=y>>31&0x1;
int c1=(a&~b); // Indicates that x is  y is+
int c2=(~a&b); // Represents x + y
Let’s calculateyx
Here are some situations to consider
yx >= 0
That is, the 32nd bit is 0flag=y+(~x+1)>>31=0
At this time, ifc2
If C2 is 1, it means that C2 is overflowed. We should return0
Ifc2=0
Then we should return 1yx <0
beflag=1
Return 0
Therefore, there are the following codes
int e=y+(~x+1); // xy;
int flag=e>>31; // If the flag is different from C2, it indicates that the overflow is
return c1 (!c2&!flag);
1.9 logicalNeg
/*
* logicalNeg  implement the ! operator, using all of
* the legal operators except !
* Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
* Legal ops: ~ & ^  + << >>
* Max ops: 12
* Rating: 4
*/
thinking
if x!=0 return 0 else return 1
Then the question becomes how to judgex!=0
Let’s take a look first~x+1>>31
As long as X= 0, then all of them are – 1, and only when x = 0 is 0
So we usex (~x+1>>31)
If – 1, it means X= 0 means x = 0
int logicalNeg(int x) {
return ((x  (~x +1)) >> 31) + 1;
}
1.10 howManyBits
/* howManyBits  return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(298) = 10
* howManyBits(5) = 4
* howManyBits(0) = 1
* howManyBits(1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^  + << >>
* Max ops: 90
* Rating: 4
*/
thinking
This problem is to find the number of bits from right to left, the leftmost 1, and then add a sign bit. If it is a negative number, we take it negative, and then do the same operation. Then there will be the following situations
X in [0,1]
We need twoX in [2,3]
We need threeX in [4,7]
We need four Summary formula\(2^i\leq x \leqslant 2^{i+1}1\)need
i+2
position
For the high 16 bits, we do this
x=(flag&~x)(~flag&x); // If x is a non positive number, it remains unchanged, and if x is a negative number, it is equivalent to bitwise negation
int b16=!! (x>>16) <<4; // If the upper 16 bits are not 0, let B16 = 16
x>>=b16; // If the upper 16 bits are not 0, we move the 16 bits to the right to see the upper 16 bits
Then look at the high 8 bits, and the following processing is basically similar
//The following process is basically similar
int b8=!!(x>>8)<<3;
x >>= b8;
int b4 = !!(x >> 4) << 2;
x >>= b4;
int b2 = !!(x >> 2) << 1;
x >>= b2;
int b1 = !!(x >> 1);
x >>= b1;
int b0 = x;
return b0+b1+b2+b4+b8+b16+1;
I suggest you manually simulate this process
1.11 floatScale2
//float
/*
* floatScale2  Return bitlevel equivalent of expression 2*f for
* floating point argument f.
* Both the argument and result are passed as unsigned int's, but
* they are to be interpreted as the bitlevel representation of
* singleprecision floating point values.
* When argument is NaN, return argument
* Legal ops: Any integer/unsigned operations incl. , &&. also if, while
* Max ops: 30
* Rating: 4
*/
thinking
1. First consider the first case
When argument is NaN, return argument
You need to find out firstexp
int exp = (uf&0x7f800000)>>23; // 2330 these eight
int sign=uf>>31&0x1; // Sign bit
int frac=uf&0x7FFFFF;
Ifexp=255
And the mantissa is nonzeroNaN
Just return directly. Secondly, iffrac
All 0 means infinity. You can return directly in both cases

If
exp=0
Denormalization number
So let’s go straight back
uf*2
Just putfrac>>1

If
exp!=0 && ！=255
Then it represents the normalized number
Then let’s change it firstexp+1
unsigned floatScale2(unsigned uf) {
unsigned exp = (uf&0x7f800000)>>23;
unsigned sign=uf>>31&0x1;
unsigned frac=uf&0x7FFFFF;
unsigned res;
if(exp==0xFF)return uf;
else if(exp==0){
frac <<= 1;
res = (sign << 31)  (exp << 23)  frac;
}
else{
exp++;
res = (sign << 31)  (exp << 23)  frac;
}
return res;
}
1.12 floatFloat2Int
/*
* floatFloat2Int  Return bitlevel equivalent of expression (int) f
* for floating point argument f.
* Argument is passed as unsigned int, but
* it is to be interpreted as the bitlevel representation of a
* singleprecision floating point value.
* Anything out of range (including NaN and infinity) should return
* 0x80000000u.
* Legal ops: Any integer/unsigned operations incl. , &&. also if, while
* Max ops: 30
* Rating: 4
*/
thinking
The format of 6bit IEEE floating point number is as follows
According to the above figure, we can divide it into three cases
First calculateE=expbias

If decimal
E< 0
In this case, we directly return 0 
If it is
exp=255
Return directly to0x80000000u
Note here that if the range is exceeded, 0x8000000u will also be returned directlyTherefore, it can be used directly
E>=31
To judge 
If it is a normalized number, we perform normal processing\(V=(1)^s \times M \times 2^E\)
 First add the omitted 1 to the mantissa
 judge
E<23
The mantissa needs to be rounded off23E
position  Just return according to the sign bit
int floatFloat2Int(unsigned uf) {
unsigned exp = (uf&0x7f800000)>>23;
int sign=uf>>31&0x1;
unsigned frac=uf&0x7FFFFF;
int E=exp127;
if(E<0)return 0;
else if(E >= 31){
return 0x80000000u;
}
else{
frac=frac1<<23;
If (e < 23) {// rounding required
frac>>=(23E);
}else{
frac <<= (E  23);
}
}
if (sign)
return frac;
else
return frac;
}
1.13 floatPower2
/*
* floatPower2  Return bitlevel equivalent of the expression 2.0^x
* (2.0 raised to the power x) for any 32bit integer x.
*
* The unsigned value that is returned should have the identical bit
* representation as the singleprecision floatingpoint number 2.0^x.
* If the result is too small to be represented as a denorm, return
* 0. If too large, return +INF.
*
* Legal ops: Any integer/unsigned operations incl. , &&. Also if, while
* Max ops: 30
* Rating: 4
*/
thinking
According to the above figure, we can draw several boundaries
x>127
Return + NanX is too small, return 0
x>=126
Normalized number Otherwise, it is denormalized
unsigned floatPower2(int x) {
if(x>127){
return 0xFF<<23;
}
else if(x=126){
int exp = x + 127;
return (exp << 23);
} else{
int t = 148 + x;
return (1 << t);
}
}
The result shown in the figure above is correct