嵌入式C语言自我修养之数据存储

本文是个人阅读学习《嵌入式C语言自我修养》王利涛 后做出了一些列的总结,方便个人查阅。它所面向的读者应该已经具备了一些嵌入式C语言编程经验,并想提高一些C语言编程技巧。

1.1数据类型与存储

类型,是一组数值及对该数组数值进行各种操作的集合。同一种类型的数据,在不同的处理器平台下,存储方式可能不一样。不同类型的数据,在同一处理器平台下,存储方式和运算规则也可能不一样。

1.1.1 大端模式与小端模式

在计算机中,位(bit)是最小的存储单位,在一个DDRSDRAM内存电路中,通常使用一个电容器来表示:充电时高电位表示1,放电时低电位表示0。8个bit组成一字节(Byte),字节是计算机最基本的存储单位,也是最小的寻址单元,计算机通常以字节为单位进行寻址。在一个32位的计算机系统中,通常4字节组成一个字(Word),字是软件开发者常用的存储单位。

我们使用C语言提供的int关键字,可以定义一个整型变量。

> int i = 0x12345678

编译器根据变量i的类型,在内存中分配4字节大小的在储空间来存储i变量的值0x12345678。一个数据在内存中有2种存储方式:高地址在储高字节数据,低地址存储低字节数器;或者高地址存储低字节数据,而低地址则存储高字节数据,不同字节的数据在内存中的存储顺序被称为字节序。根据字节序的不同,我们一般将存储模式分为大端模式和小端模式。

在这里插入图片描述
不同架构的处理器,存储模式一般也不同。ARM、X86、DSP一般都采用小端模式,而 IBM、Sun、PowerPC 架构的处理器一般都采用大端模式。如何判断程序运行的当前平台是大端模式还是小端模式呢?很简单,我们编写一个程序测试一下就可以知道。

#include <stdio.h>

int main(void)
{
	int a = 0x11223344;
	char b;
	b =a;
	if(b == 0x44)
		printf("Little endian!\n");
	else
		printf("Big endian!\n");
	return 0;
}

如果打印的是a = 0x11则是小端模式,如果打印的是a = 0x88则是大端模式。

作为一名嵌入式工程师,掌握大端模式与小端模式的存储方式很有必要。我们在驱动开发中配置各种寄存器,经常需要对某个寄存器的几个比特位进行读写操作。不同存储模式的嵌入式设备互联及网络数据传输,也需要考虑大小端模式,在处理网络数据时需要自己实现数据的大小端转换。如果你写的程序代码要在不同架构的嵌入式平台上运行(如ARM、PowerPC),还是要考虑到大小端模式的转换的。

在一个嵌入式系统软件中,如何实现大小端存储模式的转换呢?我们可以定义一个宏,将高、低地址上的数据互换,即可完成大小端存储模式的转换。

#define swap_endian_u16(A)        \           ((A & 0xFF00 >> 8) | (A & 0x00FF << 8))

1.1.2 有符号数和无符号数

天有阴晴,月有圆缺,人分男女,数分正负,我们生活在一个二的世界。C 语言为了能表示负数,引入了有符号数和无符号数的概念,在声明数据类型时分别使用关键字signed和unsigned 修
饰。我们定义的变量如果没有使用 signed或unsigned 显式修饰,默认是 signed型的有符号数。

一个字符型的有符号数,最高的位 bit7 是符号位:0表示正数,1表示负数,其余的比特位用来表示大小。而一个字符型的无符号数,所有的比特位都用来表示数的大小。因此有符号数和无符号数能表示的数值范围是不一样的,对于一个字符型数据而言,有符号数能表示的数值范围为[-128,127],而无符号数的数值范围为[0,255]。我们使用printf()函数打印数据时可以使用%d和%u格式符分别格式化打印有符号数和无符号数。

#include <stdio.h>
int main(void)
{
	signed int a = -1;
	int b = 0xffffffff;
	printf("a = %d\n",a);   //有符号数打印
	printf("a = %u\n",a);	//无符号数打印
	
	printf("b = %d\n",b);   //有符号数打印
	printf("b = %u\n",b);	//无符号数打印
	return 0}

打印出来的效果如下图

a = -1
a = 4294967295
b = -1
b = 4294967295

对于我们定义的变量,编译器在编译程序时会根据变量的类型对数据进行编码,分配合适的存储空间,把数据存储在内存中。当 printf()函数解析数据时,如果你使用和该数据类型相匹配的打印格式,则可以正确打印这个数据的值:如果你使用其他打印格式打印,则 printf0函数可能就把它解析成另外一个值了。总而言之,它在内存里就是一串二进制数据 0 和 1,关键看如何去解析它。

1.1.3 数据溢出

每一种数据类型都有它能表示的数据范围。一个有符号字符型变量,它能表示的数值范围为[-128,127],如果我们把130赋值给这个有符号型的字符变量,则会发生什么情况?

#include <stdio.h>
int main(void)
{
	char i;
	for(i = 0; i < 130; i++)
	printf("*");
	return 0;
}

编译运行上面的程序,你会发现程序陷入了死循环,一直在不断打印。当我们给一个变量赋一个超出其能表示范围的数时,就会发生数据溢出,如上面的示例代码所示,导致程序运行出现异常。当数据溢出时,到底发生了什么状况,导致上面的程序陷入了死循环呢?
一般来讲,无符号数溢出时会进行取模运算,继续“周期轮回”。例如,一个 unsigned char类型的数据,它能表示的数据范围为[0.255],当其循环到255 最大值时继续加1,这个数就变成了0,开始新的一轮循环,周而复始。

#include <stdio.h>
int main(void)
{
	unsigned char c = 255;
	printf("c= %u\n",c);
	c++;
	printf("C %u\n",c);
	return 0;
}

程序运行如下。

c = 255
c = 0

而对于有符号数,当发生数据溢出时,由于 C 语言的语法宽松性,不对数据类型做安全性检查,因此也不会触发异常,但是会产生一个未定义行为。
什么是未定义行为呢?通俗点理解,就是遇到这种情况时,C 语言标准也没有规定该如何操作,各家编译器在处理这种情况时也就没有了参考标准,各自按照自己的方式处理,编译器都不算错误。这也导致了当有符号数发生溢出时,运行结果是不确定的,在不同的编译器环境下编译运行,结果可能不一样。

#include <stdio.h>
int main(void)
{
	signed char c2 = 127;
	printf("c2 = %d\n",c2);
	c2++;
	printf("c2 = %d\n",c2);
	return 0}

大家可以尝试在不同的编译器环境下编译运行上面的程序,你会发现大部分结果都是-128,也就是说大部分编译器都默认采用了与无符号数一样的轮回处理。但是如果有一家编译器比较特殊,编译运行后的结果是 0,你也不能算错。
数据溢出可能会导致程序的运行结果和你预期的不一样,有时候甚至会改变程序的运行路径,因此在实际编程中,我们要时刻注意数据溢出的问题。

如何防范数据溢出呢?方法其实很简单,我们先看看两个有符号数相加的情况。如果两个正数相加的和小于 0,说明运算过程中发生了数据溢出。同理,如果两个负数相加的和大于 0,也说明数据发生了溢出。

#include <stdio.h>
int main(void)
{
	char a= 125;
	char b= 30;
	char c=a+b;
	if(c<0)
		printf("data overflow!\n");
	else
		printf("%d\n",c);
	return 0;
}

对于无符号数的相加,如果两个数的和小于其中任何一个加数,此时我们也可以判断数据在计算过程中发生了溢出现象。

int main(void)
{
	unsigned char a = 255;
	unsigned char b = 255;
	unsigned char c;
	c = a+b;
	if(c < a || c < b)
		printf("data overflow!\n");
	else
		printf("c = %u\n",c );
	return 0;
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
宋宝华嵌入式 C/C++语言精华文章集锦 C/C+语言 struct 深层探索 ............................................................................2 C++中 extern "C"含义深层探索........................................................................7 C 语言高效编程的几招...............................................................................11 想成为嵌入式程序员应知道的 0x10 个基本问题 .........................................................15 C 语言嵌入式系统编程修炼...........................................................................22 C 语言嵌入式系统编程修炼之一:背景篇 ............................................................22 C 语言嵌入式系统编程修炼之二:软件架构篇 ........................................................24 C 语言嵌入式系统编程修炼之三:内存操作 ..........................................................30 C 语言嵌入式系统编程修炼之四:屏幕操作 ..........................................................36 C 语言嵌入式系统编程修炼之五:键盘操作 ..........................................................43 C 语言嵌入式系统编程修炼之六:性能优化 ..........................................................46 C/C++语言 void 及 void 指针深层探索 .................................................................50 C/C++语言可变参数表深层探索 .......................................................................54 C/C++数组名与指针区别深层探索 .....................................................................60 C/C++程序员应聘常见面试题深入剖析(1) ..............................................................62 C/C++程序员应聘常见面试题深入剖析(2) ..............................................................67 一道著名外企面试题的抽丝剥茧 ......................................................................74 C/C++结构体的一个高级特性――指定成员的位数 .......................................................78 C/C++中的近指令、远指针和巨指针 ...................................................................80 从两道经典试题谈 C/C++中联合体(union)的使用 ......................................................81 基于 ARM 的嵌入式 Linux 移植真实体验 ................................................................83 基于 ARM 的嵌入式 Linux 移植真实体验(1)――基本概念 ...........................................83 基于 ARM 的嵌入式 Linux 移植真实体验(2)――BootLoader .........................................96 基于 ARM 的嵌入式 Linux 移植真实体验(3)――操作系统 ..........................................111 基于 ARM 的嵌入式 Linux 移植真实体验(4)――设备驱动 ..........................................120 基于 ARM 的嵌入式 Linux 移植真实体验(5)――应用实例 ..........................................135 深入浅出 Linux 设备驱动编程 .......................................................................144 1.Linux 内核模块..............................................................................144 2.字符设备驱动程序 ...........................................................................146 3.设备驱动中的并发控制 .......................................................................151 4.设备的阻塞与非阻塞操作 .....................................................................157

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值