《嵌入式Linux与物联网软件开发——C语言内核深度解析》一1.5 C语言如何操作内存

1.5 C语言如何操作内存

1.5.1 C语言对内存地址的封装

前面一直谈内存,其间虽然穿插一些C语言的内容,但还不够细致、深入。本节将深入分析C语言的内存地址封装,如用变量名来访问内存、数据类型的含义、函数名的含义。下面以C代码实例分析。

int a;
a = 5;
a += 4;            // 结果a等于9

下面结合内存来解析C语言语句的本质。

int a:编译器帮我们申请了一个int类型的内存格子(长度是4字节,地址是确定的,但是只有编译器知道,我们是不知道的,也不需要知道),并且把符号a和这个格子绑定。

a = 5:编译器发现我们要给a赋值,就会把这个值5丢到符号a绑定的那个内存格子中。

a += 4:编译器发现我们要给a加值,a += 4等效于a = a + 4,编译器会先把a原来的值读出来,然后给这个值加4,再把加之后的和写入a里面去,最后这个格子里面存储的内容就是9。

C语言中数据类型的本质含义,是表示一个内存格子的长度和解析方法。

数据类型决定长度的含义,如一个内存地址(0x30000000),本来只代表一个字节的长度,但是实际上我们可以通过给它一个类型(int),让它有了长度(4),这样这个代表内存地址的数字0x30000000,就能表示从这个数字开头的连续n(4)个字节的内存格子了,即0x30000000 + 0x30000001 + 0x30000002 + 0x30000003。

数据类型决定解析方法的含义是:假如有一个内存地址(0x30000000),我们可以通过给这个内存地址不同的类型来指定这个内存单元格子中二进制数的解析方法。如int的含义就是0x30000000 + 0x30000001 + 0x30000002 + 0x30000003这四个字节连起来共同存储的是一个int型数据;那float的含义就是0x30000000 + 0x30000001 + 0x30000002 + 0x30000003这四个字节连起来共同存储的是一个float型数据。

int a;时,编译器会自动分配一块内存出来,假设这里是32位操作系统,那么int就是4个字节,如果这块内存的第一个字节(首字节)地址为0x12345678,编译器会将变量名a与这个首字节地址绑定,对a进行存取与操作,实际上就是向0x12345678开始的4个字节空间进行读写操作。

float a;
(int *)a;         // 等价于分配一块指针类型的空间,并且把地址和变量名关联

最后我们谈谈C语言中的函数,不知道你是否思考过C语言中函数调用是如何实现的,主调函数是如何找到那些被调函数的。在C语言中,函数就是一段代码的封装。函数名的实质就是这一段代码的首地址,所以说函数名的本质也是一个内存地址。有了函数名(指针),也就是有了地址,我们才实现了函数的调用。

1.5.2 用指针来间接访问内存

我们常常把C语言中的指针列为难点对象,很多人始终搞不清楚指针,其实这就需要你掌握我们前面讲的那些东西。下面我们就以前面的知识为基础,啃下这根难啃的骨头。

指针是什么?我们的回答是指针就是地址。说得再全面一点,指针是一个变量,且这个变量是专门用来存放地址的。这就好比你想给A打电话,但你不知道A的电话号码,但你知道C有A的电话号码,而且你也有C的电话,这样你就可以间接地通过C来找到A。指针也是如此。通过下面的例子我们就可以看出用指针变量p来间接地获取了变量a的内容。

# include <stdio.h>
    int main (void)
    {
        int a=5;            // 开辟一块整型类型的内存空间,里面放入数据5
                               并且把该内存空间的地址和变量名a相关联
        int *p=&a;          // 开辟一块指针类型的内存空间,里面放入a的
                            // 地址数据,并且把空间地址和变量名p相关联
        printf("%d",p);    // 是取内容符,也就是取一个地址所对应空
                            // 间里面的内容,p就是(a的地址)也就是取a地
                            // 址所对应空间里的内容,那就是5
        return 0;
    }

1.5.3 指针类型的含义

C语言中的指针,全名叫指针变量,指针变量其实和普通变量没有任何区别(不管int float等,还是指针类型int 或者float 等)。只要记住:类型只是对其所修饰的数字或者符号所代表内存空间的长度和解析方法的规定。如int a和int p其实没有任何区别,a和p都代表一个内存地址(如0x20000000),但是这个内存地址0x20000000的长度和解析方法不同。a和b的空间大小虽然都是4个字节(碰巧),但是解析方法是截然不同的,前者解析方法是按照int的规定来的;后者按照int 方式解析。对于int *p来说,表示变量p里面存放的是一个地址,这个地址指向的空间用于存放一个int型的整数。

1.5.4 用数组来管理内存
数组和变量其实没有本质区别,只是符号的解析方法不同(普通变量、数组、指针变量其实都没有本质差别,都是对内存地址的解析,只是解析方法不一样)。我们知道数组定义就是在内存中开辟一块连续的内存空间,并且数组名就是其开辟的内存空间的首地址。也就是说,数组名就相当于一个指针,它里面放着这个数组的首元素地址(如&a[0])。由此定义我们可以知道,定义数组就是一次性定义一堆变量,并且这些变量在内存中开辟的空间地址是连续的,第一个变量a[0]的地址记录在数组名a中。

int a;         // 编译器分配4字节长度给a,并且把首地址和符号a绑定起来
    int a[10];     // 编译器分配40个字节长度给a,并且把首元素首地址和符号a绑定起来

下面我们做个实验:为了大家更加容易理解,我们把一个数组中的元素地址输出看看。

  ```javascript

include

    int main (void)
    {
        int a[5]={5,4,3,2,1};//定义数组
        printf("%p,%p,%p,%p,%p,%p\n",a,&a[0],&a[1],&a[2],&a[3],&a[4]);
        // %p是以地址形式输出(也就是16进制)。在我的计算机中运行
        // 后输出的结果是:
        // 0018FF34,0018FF34,0018FF38,0018FF3C,0018FF40,0018FF44
        // 我们可以看出,在当前编译器下,编译器为每一个元素分配4
        // 个字节的空间
        // 并且它们的地址是连续的。也可以看出数组名就是首元素
        // 的地址
        return 0;
    }
下面我们给出对应上面程序的内存逻辑图,前面为了便于大家理解,内存地址全部是十进制,实际上,我们一般用十六进制表示内存地址。

<div style="text-align: center">
 <img src="https://yqfile.alicdn.com/2b54f606a39d8dd6a44b6f673b04c771eb35e537.png" >
</div>

8 位内存逻辑模型

数组中第一个元素(a[0])称为首元素;每一个元素类型都是int,所以长度都是4,其中第一个字节的地址称为首地址;首元素a[0]的地址称为首元素地址。由上面的图可以看出,第三个元素的地址也就是a[2]的地址,可以由第一个元素a[0]的地址推出,因为数组元素的地址是连续的。也就是a[0]的地址(a)加1就是a[1]的地址,加2就是a[2]的地址。可能此时你就有疑问了,不对啊,a[0]的地址加1不是0x0018FF35吗?其实不是的,我们一直强调数据类型的重要性,其中一点就是解析方法。int*的数据类型,编译器解析的时候就知道是以4个字节的跨度来依次分配。你写的a+1,编译器就会明白是指a[1]的地址,也就是首地址加4。你写a+2,编译器就会明白是a[2]的地址,也就是首地址加8,依次类推。如果你还是不够明白,我们举个例子。当你确定我们是一个人的时候,那我们就是一个完整的人,此时对于我们的划分就是一个人、两个人, 不能是1/2个、1/3个。当然我们不是单纯地为了研究数组中元素的地址,我们的最终目的是想通过元素中的地址找到该元素的值。

    #include <stdio.h>
    int main (void)
    {
          int a[5]={5,4,3,2,1};    // 定义数组
          printf("%p,%p,%p,%p\n",a,a+1,a+2,a+3);
          // 输出结果是:0018FF34,0018FF38,0018FF3C,0018FF40
          // 以首地址为基础,依次加1,加2,加3,也就是a[1],a[2],a[3]的地址
          printf("%d,%d,%d,%d\n",a[0],*a,*(a+1),*(a+3));
          // 第一个应该输出5,也就是首元素a[0]
          // 第二个也是5,原因是a相当于指针变量,里面放着a[0]的地址
          // 所以取该地址所对应的内容,也就是a[0]的值
          // 第三个值是4,*(a+1)取(a+1)地址里面的内容,也就是a[1]的值4
          // 第四个输出是2,取a+3后的地址所对应的内容,a+3后的地址也就
          // 是a[3]的地址。取其内容就是a[3]的值:2
          return 0;}
时间: 2024-11-03 16:27:39

《嵌入式Linux与物联网软件开发——C语言内核深度解析》一1.5 C语言如何操作内存的相关文章

嵌入式Linux与物联网软件开发——C语言内核深度解析》一1.7 内存管理之栈(stack)

1.7 内存管理之栈(stack) 1.7.1 什么是栈 我们常常听人说堆栈,但大家一定要明确区分:堆就是堆,栈就是栈.我们平常说的堆栈一般是指栈.那栈的本质是什么?栈是一种数据结构,C语言中使用栈来保存局部变量(注意,下文中不强调的情况下,局部变量均指非静态局部变量).栈是被发明出来管理内存的,是一种维护内存的机制,这就是栈的本质. 1.7.2 栈管理内存的特点(小内存.自动化) 栈的特点是入口即出口,只有一个口,另一个口是堵死的.所以先进去的后出来,也就是先进后出,而与栈特点相对的就是先进先

《嵌入式Linux开发实用教程》——1.5 嵌入式Linux移植常用软件

1.5 嵌入式Linux移植常用软件 在进行嵌入式Linux学习与开发的过程中,需要使用到一些常用的开发工具,熟练使用这些软件,能让学习与开发达到事半功倍的效果. 1.5.1 SecureCRT SecureCRT是可以在Window环境下登录UNIX和Linux服务器主机的软件,它不仅支持SSH1.SSH2,而且支持TeInet和rlogin协议. 在Ubuntu宿主机上安装SSH. zhuzhaoqi@zhuzhaoqi-desktop:~/sudo apt-get install open

专为VB制做的多语言软件开发工具,可以轻松完成对可视界面的多语言化.

专为VB制做的多语言软件开发工具,可以轻松完成对可视界面的多语言化,您只需要制做语言包母板后翻译复制该母板即可.点这里下载示例工程. 界面介绍如下: 启动图标:点这里将启动该程序. 程序将向您的代码中添加以下代码,以便您访问语言包: API:  Private Declare Function GetPrivateProfileString Lib "kernel32" Alias "GetPrivateProfileStringA" (ByVal lpApplic

转: 嵌入式linux下usb驱动开发方法--看完少走弯路【转】

转自:http://blog.csdn.net/jimmy_1986/article/details/5838297 嵌入式linux下的usb属于所有驱动中相当复杂的一个子系统,要想将她彻底征服,至少需要个把月的时间,不信?那是你没做过. 本人做过2年的嵌入式驱动开发,usb占了一大半的时间.期间走了不少弯路,下面将我的血的经验教训总结下,为要从事和正在从事的战友们做一点点贡献吧:) 首先,扫盲: 要做的是阅读usb Spec(英文的哦,其实很多文章.书籍和资料真有水平的还是原创的好,就像食品

软件开发-安卓,jsoup,解析网页中的javascript的内容,提取信息

问题描述 安卓,jsoup,解析网页中的javascript的内容,提取信息 开发-安卓,jsoup,解析网页中的javascript的内容,提取信息-jsoup解析javascript"> 这个是网页地址:http://www.bilibili.com/mobile/subchannel.html#tid=33 以下是抓取到的网页源码,以图片发出,实在不好意思了,可是好像因为这个而发不出来了,现在安全起见还是以图片的形式发出,或者可以上该链接获取,我是用ie仿真为windows phon

Linux下的软件开发_unix linux

    1.如何升级.编译内核? 如果你不想改变内核的版本,直接转到以下第四步. 1)在任何一个子目录下(但通常是在/usr/src/下)untar解开新的内核源程序: tar xvfz linux-x.x.xx.tar.gz (例如linux-2.0.35.tar.gz) 或者是打补丁(patch):  gzip -cd patch-2.0.35.gz | patch -p0 打完补丁直接跳到第四步 2)rm /usr/src/linux   这通常是一个符号连接. 3)在 /usr/src/

Linux下的软件开发

1.如何升级.编译内核? 如果你不想改变内核的版本,直接转到以下第四步. 1)在任何一个子目录下(但通常是在/usr/src/下)untar解开新的内核源程序: tar xvfz linux-x.x.xx.tar.gz (例如linux-2.0.35.tar.gz) 或者是打补丁(patch): gzip -cd patch-2.0.35.gz | patch -p0  打完补丁直接跳到第四步 2)rm /usr/src/linux 这通常是一个符号连接. 3)在 /usr/src/ 下建立一个

《嵌入式Linux开发实用教程》——导读

前言 嵌入式Linux开发实用教程 2012年11月,当我看到论坛中的同龄大学生在学习嵌入式Linux寸步难行,我就计划将我学习嵌入式Linux的点点滴滴记录下来,从一个学生的角度去写,或许更能让初学者接受.2013年1月,当写完初稿再重新审视的时候,总感觉不尽如意.2013年3月,我联系了我的师弟李强,两人打算以一个全新的思维重新完成这本书. 2013年6月,书稿终于定型. 本书一共有6章,从Linux指令基础到Linux常用软件:从U-Boot移植到Linux移植:从Linux驱动程序设计到

基于ARM的嵌入式linux系统设计

基于ARM的嵌入式linux系统设计 摘要:本文简要介绍了ARM处理器的特点及其基本结构,详细论述了基于ARM的嵌入式linux系统的关键技术,包括引导加载程序.Linux内核.文件系统.用户应用程序.对linux系统的各部分开发设计做了较深入清晰地分析,总结了linux系统的特点,及其在嵌入式操作系统竞争中的优势.   关键字:ARM   linux  引导加载程序 内核 文件系统       Design of the embedded linux system based ARM