CS:APP Chart II 32-64 <自用>

文章通过C语言代码展示了如何检查整数加法和减法的溢出,以及如何利用位操作进行字节级别的数据处理,包括查看内存中的字节表示、判断字节序和替换字节。此外,还讨论了算术右移和逻辑右移的区别,并提供了相应函数的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

#include <stdio.h>
#include <limits.h>

int tadd_ok(int x, int y);
int tsub_ok(int x, int y);

int main(void)
{
	int x, y;
	
	x = INT_MAX;
	y = 3;
	
	printf("%d\n", tsub_ok(x, y));
	return 0;
}

int tadd_ok(int x, int y)	//后续我的采用的皆是0代表溢出,1代表正常
{
	int s = x + y;
	if (x >= 0 && y >= 0)	//溢出的可能条件		
		return s >= 0;
	else if (x < 0 && y < 0)	//溢出的可能条件	
		return s <= 0;
	else	//不可能溢出
		return 1;
}

int tsub_ok(int x, int y)
{
	return tadd_ok(x, -y);
}




#include <stdio.h>
#include <limits.h>
#include <stdint.h>

int tmult_ok(int x, int y);

int main(void)
{
	int x, y;
	
	x = 100;
	y = INT_MAX;
	
	printf("%d\n", tmult_ok(x, y));
	
	return 0;
}

int tmult_ok(int x, int y)
{
	int64_t p = (int64_t) x * (int64_t) y;	//假设int为32bit的拳皇下
	return p == (int) p;					//当p的int不等于p时,那么说明int存不下p由此发生溢出	
}											//但是有没有可能在int64_t也发生了溢出,而等于在int中溢出后的值
											//我们知道,int*int的最大值为(-2^31)*(-2^31)=2^62,而int64_t最大数为:2^63-1,由此不会放生溢出
											//由此当int*int溢出时,p不会溢出,由此p>int64_t(int(p)),即p!=int64_t(int(p))
											//由此我们可以证明:x*y发生溢出->p!=int(p),而易知道当p!=int(p)时,x*y溢出
											//综上 x*y不溢出<->p=(int)p 




#include <stdio.h>

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, size_t len)
{
	size_t i;
	for (i = 0; i < len; i++)
		printf(" %.2x", start[i]);	//start[i]=*(start+i),而i的跨度与&start类型有关,此start为unsigned char*,则i的实际跨度为1byte=8bit=2hex
	printf("\n");					//而内存单元则为1byte,此输出的时一内存单元里转化为十六进制的二进制数								//
}									//再者此实验机器采用的小端协议,由此类如0x1234567,则在一段内存中储存为67 45 23 01(4bytes)

void show_int(int x)
{
	show_bytes((byte_pointer) &x, sizeof(int));
}

void show_float(float x)
{
	show_bytes((byte_pointer) &x, sizeof(float));
}

void show_pointer(void *x)
{
	show_bytes((byte_pointer) &x, sizeof(void *));
}

void show_double(double x)
{
	show_bytes((byte_pointer)&x, sizeof(double));
}

void test_show_bytes(int val)
{
	int ival = val;
	float fval = (float) ival;	//float也为4bytes
	int *pval = &ival;
	// int64_t i64_tval = (int64_t) val;	//我的gcc太老了,不支持C99 :-(
	double dval = (double) val;
	show_int(ival);
	show_float(fval);
	show_pointer(pval);	
	// show_bytes(i64_tval);
	show_double(dval);
}

void test_len_pointer(void)
{
	printf("the len of pointer is %d\n", sizeof(void*));	//实验机器为4bytes
}

int main(void)
{
	test_show_bytes(12345);	//12345=0x3039
	test_len_pointer();
	return 0;
}



/*
	编写过程is_little_endian,当在小端法机器上编译和运行时返回1,
	在大端法机器上编译运行时则返回0。
	这个过程应该可以运行在任何机器上,无论机器的字长是多少。
*/

#include <stdio.h>



int is_little_endian(void)
{
	int n = 1;		//n = 01 00 00 00
	int* p = &n;	//没搞懂,原因在于无法理解(void*)p的意义
	// int* v = (void *) p;		//原程序
	unsigned char* v = (unsigned char*)p;	//我修改的,我认为*v会显示p指向int数据的第一个字节
	
	if (*v == 0x01)							//如果是小端的话,则指向01,如果是大端的话,则指向00
		return 1;
	else
		return 0;
}


// int main(void)
// {
// 	int flag = is_little_endian();

// 	if (flag == 1)
// 		printf("The Machine is Little Endian\n");
// 	else if (flag == 0)
// 		printf("The Machine is Big Endian\n");
// 	else
// 		printf("Something Wrong\n");

// 	return 0;
// }




/*
	编写一个C表达式,生成一个字,由x的最低有效字节和y中剩下的字节组成。
	对于运算数x = 0x89ABCDEF 和 y = 0x76543210,就得到0x765432EF.
*/

#include <stdio.h>

int procedure(int x, int y);

int main(void)
{
	printf("%#x\n", procedure(0x89ABCDEF, 0x76543210));
	return 0;	//%#x的作用是将整数输出为0xxxxxxxx的形式,其中0x的实现是由#符号导致,十六进制的转换是由x的导致
}

int procedure(int x, int y)
{
	int mask = 0xFF;					//首先我知道二进制掩码的道理,而十六进制的道理可以简化为四位二进制掩码的证明
										//0000&xxxx=0000 1111&xxxx=xxxx 由此0&x=0 f&x=x
	return (~mask & y) | (mask & x);
}




/*
	假设将一个w位的字中的字节从0(最低位)到w/8 - 1(最高位)编号。		//w/8即byte
	编写c函数
		unsigned replace_byte(unsigned x, int i, unsigned char b);
	以下为示例:
		replace_byte(0x12345678, 2, 0xAB) --> 0x12AB5678
		replace_byte(0x12345678, 0, 0xAB) --> 0x123456AB
*/

// 与 p58.c 一起编译		//why?因为用到了is_little_endian()函数!

#include <stdio.h>		//链接方法:1.将所需文件生成目标文件 gcc -c filename
						//		  2.将所需目标文件合成生成一个可执行文件 gcc file_1.o ... file_n.o -o name_of_exe

unsigned replace_byte(unsigned x, int i, unsigned char b);
int is_little_endian(void);

int main(void)
{
	
	getchar();	//ignore
	return 0;
}


unsigned replace_byte(unsigned x, int i, unsigned char b)
{
	unsigned int *p = &x;
	unsigned char *v = (unsigned char *) p;
	
	if (is_little_endian())		//这就是可移植性的体现
		v[i] = b;				//小端低字节在低字节在低地址,高字节在高地址
	else 
		v[sizeof(unsigned) - i - 1] = b;
	
	return *p;
}



/*
	写一个C表达式,在下列描述的条件下产生1,而在其他情况下得到0。
	假设x是int类型。
		A. x的任何位都等于1
		B. x的任何位都等于0
		C. x的最低有效字节中的位都等于1
		D. x的最高有效字节中的位都等于0
*/

#include <stdio.h>

int expression(int x);
int xor(unsigned int a, unsigned int b);

int main(void)
{
	printf("0xffffffff, %d\n", expression(0xffffffff));  // 情况A
	printf("0x00000000, %d\n", expression(0x00000000));  // 情况B
	printf("0x0000000f, %d\n", expression(0x0000000f));  // 情况C
	printf("0x0fffffff, %d\n", expression(0x0fffffff));  // 情况D
	printf("0x0f0f0f0f, %d\n", expression(0x0f0f0f0f));  // 情况C D
	printf("0xfffffff0, %d\n", expression(0xfffffff0));  // 返回0
	return 0;
}

int expression(int x)
{
	int result1, result2, result3, result4;
	unsigned int mask0 = 0x00000000, mask1 = 0xffffffff, mask3 = 0xf, mask4 = 0xf0000000;
	
	result1 = !(xor(x, mask1));				//当x全1,xor后全0,值为0,取反为1 当x不全为1,则xor后不全为0,则值为1,取反为0
	result2 = !(xor(x, ~mask1));			//当x全0,xor后全0,值为0,取反为1 当x不全为0,则xor后不全为0,则值为1,取反为0
	result3 = !(xor((x & mask3), mask3));	//x&mask4只剩下最低位x存留,当x全1,xor后全0,值为0,取反为1 当x不全为1,则xor后不全为0,则值为1,取反为0
	result4 = !(xor((x & mask4), mask0));	//x&mask4只剩下最高位x存留,当x全0,xor后全0,值为0,取反为1 当x不全为0,则xor后不全为0,则值为1,取反为0
	// printf("%d %d %d %d\n", result1, result2, result3, result4);
	
	return result1 || result2 || result3 || result4;
}

int xor(unsigned int a, unsigned int b)
{
	return ((a & ~b) | (~a & b));	//a xor b = a & ~b | ~a & b
}									//注意按位取反与按位and与按位or和取反与and与or的区别
									//~ & |
									//! && ||




/*
	编写一个函数int_shifts_are_arithmetic(),在对int类型的数使用
	算术右移的机器上运行这个函数生成1,而其他情况下生成0。		//逻辑右移左补0,算数右移左补原最高位
*/															//无符号必为逻辑右移,有符号数看协议
		
#include <stdio.h>

int int_shifts_are_arithmetic();

int main(void)
{
	printf("%d\n", int_shifts_are_arithmetic());
	return 0;
}

int int_shifts_are_arithmetic(void)
{
	int n = -1;				//n = 1111 1111 1111 1111
	int shifts = (n>>2);	//如果是算数的话,则n>>2 = 1111 1111 1111 1111 = -1 = n
							//如果是逻辑的话,则n>>2 = 0011 1111 1111 1111 = 2^30 - 1 != n
	// printf("%#x, %#x\n", n, shifts);
	
	return shifts == n;		//难点在这个n的设置上,妙妙的 : )
}



/*
	函数srl用算术右移来完成逻辑右移;	//logically
	函数sra用逻辑右移来完成算术右移。	//arithmetically
*/

#include <stdio.h>

unsigned srl(unsigned x, int k);
unsigned sra(int x, int k);


int main(void)
{
	int x = 0x12345678;
	int y = -1;
	printf("%#x\n", srl((unsigned) x, 8));
	printf("%#x\n", sra(x, 8));
	printf("%#x\n", srl((unsigned) y, 8));
	printf("%#x\n", sra(y, 8));
	return 0;
}

unsigned srl(unsigned x, int k)		//accomplish the logical right shift arithmetically
{
	/* perform shift arithmetically */
	unsigned xsra = (int) x >> k;	//x算数右移k位的二进制码位给xsra
	
	int mask = -1;	//mask = 1111 1111 1111 1111
					//sizeof 返回的为size_t类型的值,size_t可以看作unsigned int
	mask = mask << ((sizeof(int) << 3) - k);	//mask左移(sizeof(int) * 8 - k) 给 mask,得到前k位1,后sizeof(int)*8-k位的0
	return ~mask & xsra;						//~mask位前k位0,后sizeof(int)*8-k位1,即为保留低sizeof(int)*8-k位的掩码	
}												//由此致使前k位为0,后sizeof(int)*8-k保留,实现逻辑右移

unsigned sra(int x, int k)		//accomplish the arithmetical right shift logically
{
	/* perform shift logically */
	int xsrl = (unsigned) x >> k;	//前k位0
	
	unsigned mask = 1;		//mask = 0000 0000 0000 0001
	int *p = &x;			
	char *temp = (void *) p;	//貌似void指针会自动转化
	
	// 判断x的最高位是否为1
	int h = (((mask << 7) & temp[sizeof(int) - 1]) == 128);		//temp[sizeof(int) - 1]在小端中为int 4byte中,以一个byte
																//0000 0001 0000 0000 & 0000 0000 0000 xxxx
	h = (-h) << ((sizeof(int) << 3) - k);						//真的难懂 :-(
	return h | xsrl;
}











评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值