Lesson 20 内存函数与数据在内存中的存储1

内存函数

memmove

memcpy类似,但是可以处理源区域与目标区域有重叠的情况。虽然memcpy在某些编译器下也可以处理重叠情况,但不是所有的编译器都可以。理论上可以用memmove代替memcpy
在处理重叠的时候,根据源地址与目标地址的相对位置,决定拷贝顺序。当目标地址是低地址的是很好,拷贝顺序由前向后。如果源地址是低地址,那么顺序就是从后向前。
看一下代码实现:

void* my_memmove(void* dst, const void* src,size_t num)
{
	assert(dst && src);
	void* ret = dst;
	if (dst < src)
	{
		while (num--)
		{
			*(char*)dst = *(char*)src;
			dst = (char*)dst + 1;
			src = (char*)src + 1;
		}
	}
	else
	{
		while (num--)
		{
			*((char*)dst + num) = *((char*)src + num);
		}
	}
	return ret;
}

memset

这个函数功能是设置内存中num个字节的空间改成目标值。例如可以将“hello bit”改成“xxxxx bit”。看个例子:

int main()
{
	char arr[] = "hello bit";
	//xxxxx bit	
	//memset(arr, 'x', 5);
	memset(arr+6, 'x', 3);
	//hello xxx
	printf("%s\n", arr);
	return 0;
}

memcmp

这个函数类似strncmp,只不过是比较对象变成了内存中的数据。同样的,看个例子:

int main()
{
	//char arr1[] = "abcdef";
	//char arr2[] = "abqwertyuiop";
	//printf("%d\n",memcmp(arr1, arr2, 3));
	int arr1[] = { 1, 0x01234503, 3, 4, 5 };
	int arr2[] = { 1, 3, 5, 7, 9 };
	printf("%d\n", memcmp(arr1, arr2, 5));
	return 0;
}

这里打印结果是0。

数据在内存中的存储

整型存储

整型编码

整型在内存中是以补码形式存储的。如何取得补码在之前的课程里已经有详细叙述,这里仅做个简单总结:

  1. 正数的原码、反码、补码相同;
  2. 负数的补码 = 原码取反+1

大小端字节序

有了补码的形式,那么如何将二进制码放入内存呢?这里介绍2种不同的存储方式:

  1. 大端:二进制的低位字节存入高地址,高位字节存入低地址;
  2. 小端:二进制的低位字节存入低地址,高位字节存入高地址;
    举个例子:
    假设需要存储一个数字:0x11223344
    存储的起始地址假设为0x12FF40。那么内存中的存储方式如下:
地址大端存储小端存储
0x12FF401144
0x12FF412233
0x12FF423322
0x12FF434411

如何判断大小端呢?除了通过编译器来查看内存之外,还可以用代码:

int check_sys()
{
	int a = 1;
	return *(char*)(&a);//小端retrun 1 大端return 0;
}

int main()
{
	if (check_sys())
		printf("小端\n");
	else
		printf("大端\n");

	return 0;
}

这里首先取a的地址,强制转换成char*之后再解引用,也就是将a地址的第一个字节取出来看值了。参考上面的表格,如果是小端存储,那么a地址里的第一个字节就是01,否则是00。这样就实现了大小端的判断。

练习

这里挑一些典型的题目做分析。

Prob 1
int main()
{
	char a = -128;

	//1000 0000
	//整型提升:
	//1111 1111 1111 1111 1111 1111 1000 0000
	//
	printf("%u\n", a);//42亿多 FFFF FF80
	printf("%d\n", a);//-128

	return 0;
}

这里分析一下。-128是整数,那么就是以补码形式存在的。

	1000 0000 0000 0000 0000 0000 1000 0000 --- -128 原码
	1111 1111 1111 1111 1111 1111 0111 1111 --- -128 反码
	1111 1111 1111 1111 1111 1111 1000 0000 --- -128 补码

但这里又是要放到char类型的变量a里,所以发生了截断。a实际得到的是:

1000 0000 

打印的时候,会发生整型提升,整型提升会将1000 0000提升为:

1111 1111 1111 1111 1111 1111 1000 0000

这个数字按照%u打印 就是打印0xFFFFFF80,是一个很大的正数。如果按照%d打印,发现和-128补码一样,那么就是-128

Prob 2
int main()
{
	char a[1000];
	int i;
	for (i = 0; i < 1000; i++)
	{
		a[i] = -1 - i;
	}
	printf("%d", strlen(a));//255
	return 0;
}

分析一下。仔细看代码,可以知道a[0] = -1, a[1] = -2...a[127] = -128, 那么a[128] = 127,a[129] = 126...直到a[255] = 0strlen返回的是\0之前的字符个数,\0是在a[255]这里,那么之前一共就是255个字符。因此打印的是255.

Prob 3
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);//转换为int,+1之后走1个字节,再强转成int*
	
	printf("%x,%x", ptr1[-1], *ptr2);//*(ptr1 -1),
	return 0;
}

分析一下,ptr1 指向的是4之后的位置,那么ptr[-1] == *(ptr -1), 因此是4
ptr2的话是a(这里是个地址)转成整型后+1,并没有跳过4个字节而是跳过1个字节。根据上面的小端存储,可以知道这个数组在内存中是这样的(地址从低到高):

01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00

那么此时,ptr2指向的内存空间(4个字节,ptr2是int*)里存的就是:

00 00 00 02

%x打印,就是打印0x2000000

atoi的实现

库函数atoi会将字符形式的数字转换为整型。它是这样工作的:

  1. 跳过字符串中的空白字符,直到碰到正负号或者数字;
  2. 将数字转换,直到下一个非数字的字符出现;
  3. 如果字符串不能转换,返回0;
  4. 如果字符串代表的数字溢出,根据正负输出整型的最大值或是最小值。
    思路很简单,按照上面的要求即可。下面是代码:
#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <limits.h>
int my_atoi(char* str)
{
	int ret = 0;
	int flag = 1;
	assert(str);
	//跳过空格和前置的0
	while (isspace(*str))
	{
		str++;
	}
	//确认是+或者-
	if (*str == '+')
	{
		str++;
	}
	else if (*str == '-')
	{
		str++;
		flag = -1;
	}
	else if (*str< '0' || *str>'9')
		return 0;
	else
		;
	while (isdigit(*str))
	{
		ret = ret * 10 + *str - '0';
		str++;
		if (ret < 0 && flag == 1)
		{
			return INT_MAX;
		}
		else if (ret < 0 && flag == -1)
		{
			return INT_MIN;
		}
	}
	return ret*flag;
}

int main()
{
	char arr1[] = "-0+102345";
	char arr2[] = "00102345";
	char arr3[] = "-00102345";
	char arr4[] = "2147483648";
	char arr5[] = "-2147483649";

	printf("%d %d %d %d %d\n", my_atoi(arr1), my_atoi(arr2), my_atoi(arr3), my_atoi(arr4), my_atoi(arr5));
	printf("%d %d %d %d %d\n", atoi(arr1), atoi(arr2), atoi(arr3), atoi(arr4), atoi(arr5));


	return 0;
}

有几个要点分析一下:
首先是转换语句:ret = ret * 10 + *str - '0'; 这里注意数字字符-‘0’之后才是整数。
其次是溢出判断部分:最好不要出现数字,直接调用库内的值,否则可读性很差。另外,如果将负溢出的部分代码改为:

		else if (ret < 0 && flag == -1)
		{
			ret = 	2147483648;
			break;
		}

也是可以正常工作的。但可以看到,ret此时的值是溢出了,实际存储的时候ret会按照无符号处理,存储时发生截断,最后返回正好是加回负号。这样是不会报错的。但如果改成:

		else if (ret < 0 && flag == -1)
		{
			ret = -2147483648;
			break;
		}

是会报错的。依据上面的说法,ret赋值时按照无符号处理,然后强行加了负号,所以会报错。经过和老师讨论,改成了上面的实现方法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值