当前位置:首页 » 编程语言 » c语言的内存模型

c语言的内存模型

发布时间: 2023-06-06 07:13:52

c语言中计算结果怎么存储

所有C函数,只要其有返回值,都是由寄存器Ax(对于32位而言为EAX,如果AX不足以容纳,则由DX保存超过AX容量的部分)保存的(也可能是值,也可能是某一地址,视返回类型而定)。
上面这个C函数程序,计算(x>y?x:y);后,该值就存在AX(EAX)中,当你在其他函数中调用该函数
后,比如
a=max(m,n);后,其实就是进入max()完成后,函数返时将计算结果存放在AX中,执行这个语句后,即就将AX的值传给了a.
其实,这也很容易验证:
在调用
a=max(m,n);
紧接着输入以下语句(注意:这中间绝对不能有其他任何函数调用和语句):
b=_AX;
printf("a=%d,b=%d",a,b);
你就可能发现,a=和b=一定是同一个值。

❷ C语言代码组成 - BSS、Data、Stack、Heap、Code、Const

一段C语言经过编译连接后,成为一段可以运行的代码,可运行的代码可以分为以下四个部分组成:全局变量/静态变量区、堆、栈、代码区。其中全局变量/静态变量区又分为未初始化变量区和初始化变量区,代码区又分为代码和常量区。即汇总下来,代码可以分为6部分组成,包括:BSS区(未初始化的全局变量/静态变量区)、Data区(实始化的全局变量区)、Stack区(栈区)、heap区(堆区)、Code区(代码区)、const区(常量区)。

一、BSS区和Data区

C语言编程中定义的全局变量、静态局部变量,就是分配在全局变量/静态变量区域,但是为什么又要分为BSS区域和Data区域呢?其实我们在定义全局或者静态变量区,有时我会对它赋初始值,有的又不会赋初始化,比如我们定义的全局变量,初始化的赋值,是怎么样写到变量区域中的,我们定义的静态局部变量,在定义时初始化后,为什么后面函数被调用,又不会再初始化呢?这个局部静态变量是怎么样实始化的,什么时候初始化的?

如果分析编译后的汇编代码,就会发现在代码运行起来后,会有一段给变量赋值的指令,这一段代码,不是我们C代码对应的汇编,而是C编译器生成的汇编译代码,这段代码的作用就是给初始化了的静态变量和全局变量进行初始化。这也是为什么全局/静态变量区域,要分BSS和Data的原因。

二、Stack区

栈是一种先进后出的数据结构,这种数据结构正好完美的匹配函数调用时的模型过程,比如函数f(a)在运行过程中调用函数f(b),f(a)在运行过程中的变量就是分配在栈中,通过在调用f(b)前,会将代码中用到的R0~Rn寄存器的值保存到栈中,同时将函数的传入参数写入到栈中,然后进入f(b)函数,函数f(b)的变量b分配在栈中,当函数运行完毕后,释放变量b,将栈中存放的f(a)函数的运行的R0~Rn寄存器值恢复到寄存器中,同时f(b)的返回结果存入到栈中,这样f(a)继续运行。当一个函数运行完毕后,它在栈中分配的临时变量会全部释放。

对于中断也是一样的,中断发生时,也是一个函数打断了另一个函数的运行,这种现场的保存(即寄存器的值),都是通过栈来完成的。所以栈的作用有:

三、Heap区

全局变量分配的内存在代码整个运行周期内都是有效的,而在栈区分配的内存在函数调用完成后,就会释放。这两种内存模型都是由编译器决定它的使用,代码是无法控制的。那有没有内存是由用户控制的,要用时,就自由分配,不用时,就自行释放?答案是肯定的,这部分内存就是堆。

用户需要使用的动态内存,就是通过malloc函数,调用分配的,在没有释放前,可一直由代码使用。当这部分内存不再需要使用时,可以通过free函数进行释放,将它归还到堆中。从这中可以看出,堆的内存,是按需分配的。这就是赋予了代码很大的自由度,但这也是会带来负作用的,比如:内存碎片化导致的malloc失败;忘记释放内存导致的内存泄露,而这些往往是致命的失误。

四、Code区

代码区就是编译后机器指令,这些指令决定了功能的执行。我们编译的代码一般是下载进flash中,但是运行,却有两种方式:在RAM中运行和在ROM中运行。 在RAM中运行,即是boot启动后,将flash中的代码复制到RAM中,然后PC指针在指到RAM中的代码中开始运行。 有时在调试时,我们可以直接将代码下载进RAM中运行进行调试,这样加快调试速度。便是大部分的情况我们的代码是从flash中开始运行的。

五、常量区

代码中的常量,一部分是作为立即数,在代码区中,但是像定义的字符串、给某数组赋值的一串数值,这些常量,就存在常量区,我们常用const来定义一个常量,即该变量不能再必变。这部分的变量,编译器一般将它定义的flash中。

六、各个区域大小的是如何决定的:

code区和const区:是由代码的大小和代码中常量的多少来决定的。

bss区和data区:这是由代码中定义的全局变量和局部变量的多少来决定的。

stack区:这个可以由使用都自行定义大小,但使用都要根据自已代码的情况,评估出一个合理的值,再定义其大小,如果定义的太小,很容易爆栈,导至代码异常,但是如果定义的太大,就容易浪费内存。

heap区:RAM剩下的部分,编译器就会作为堆区使用。

七、嵌入式代码一般启动过程

以STM32为例,通过分析其汇编启支代码,大致可以分为以下几个步骤:

如果大家想看编译扣,代码文件的组成,可以查看统后生的map文件,里面有详细的数据,包括各个函数的分配内存,BSS,Data,Stack,Heap,Text的分配情况。

如果相要了解详细的代码启动过程,可看它的启动汇编文件。

❸ 关于C语言中数组作为参数传递的疑惑~~

凑凑热闹,同意terry_tang的观点,另做些补充:

先看代码:

#include <stdio.h>段谨链

void foo(int array[2]){printf("int array[2]:\t\t%x %d\n", &array, sizeof(array));}
void bar(int array[]){printf("int array[]:\t\t%x %d\n", &array, sizeof(array));}
void baz(int (&array)[2]){printf("int (&array)[2]:\t%x %d\n", &array, sizeof(array));}

int main()
{
int a[2] = {1, 2};
printf("main::a[2]:\t\t%x %d\n", a, sizeof(a));
foo(a);
bar(a);
baz(a);

return 0;
}

解说:

foo和bar的传值方式是相同的,都是一个int*, 即一个整型指针,这可以从foo和bar里打印出的array地址和main中的不同和sizeof(array)仅为sizeof(int*)看出,只不过是外型有点儿区别。

编译器是不知道你要传递的是一个数组或是单一一个整型的地址的,这是因为C中数组的内存模型是连续存储(它并不知道传递的(首)地址之后的空间可否访问)。

所以写为foo或bar的样式仅仅是对人的一种暗示,暗示传递的是一个数组晌哪,括号里的2编译器是不会把他当回事儿的①。

采用foo中的样式,代码编写者在函数中获知传递的数组的大小,但这种暗示功能很弱,而且易使人产生误解。

比如以上的函数foo,传递大小为1个元素的数组(即单一一个整型的地址):

int x[1];
foo(x);

或传递一个大小为100的数组:

int x[100];
foo(x);

编译器都不会有任何抱怨,所以在代码工程量很大的时候,你无法保证数组传值的安全性,另外一个问题是如果你写的是商业性质的库,你无法保证客户(二次开发者)能安全地使用你的代码。

采用bar中样式,实质和foo相同,空括号给人的暗示就是它能接受的参数是一个数组,而且是一个长度不确定的一维整型数组,这相对于foo来说更为实际和真实一些(因为foo可能造成欺骗性的代码,原因见上)。

所以这种传数组的方式被多数人所采用,但一般还需多加一个参数来指定数组的大小,如:

void bar(int array[], int size);

或效仿STL的做法,传递数组的首地址和超尾指针(在遍历数组元素时很方便,且更快速、安全):

void bar(int* beg, int* end);

至于baz,它不同于foo和bar。前面已经说过,foo和bar实质是相同的,传的都是一个int*,且传值方式都是按值传递(C中只有按值传递)。

而baz却是按引用传递,传递的是一个"编译器认可的,大小为2"②的数组的引用。

foo和bar都可以改写为:

void theFact(int* array);

void theFact(int* array, int size);

按照此逻辑是不是baz可改写为这样呢?

void baz2(int* const& array); // a其实是一个int* const型指针,所以要加上const作为修饰

答案是否定的,注意上面的②,只有在C++中,函数"按引用"传递数组并"指定其大小时",[]中的数字才有意义(对编译器而言)。所以baz2 != baz:

int x[100];

baz(x); // 编译错误
baz2(x); // 可以通过

要理解这和foo, bar的不同首先要理解C++中对引用的定义: 引用就是对象本身,不存在没有引用对象的引用。所以在baz中,形参array就是实参main中的a,一切a所有的特性都是array的特性,所以sizeof(array) == sizeof(a),而且&baz::array == main::a(地址相同)。

①: C99中允许使握孙用static数组参数修饰词,如:

void foo(int x[static 10]); // x数组至少含有10个连续元素

上句中的10此时并不是可有可无的,它是编译器优化数组访问的一种暗示。

❹ C语言重新赋值内存地址会变吗

变量a的地址是不能变的,当程序被加载时,操作系统会为它分配好地址,且一经分配,不能再改变!当然,每一次程序运行时,操作系统为a分配的地址可以不同。

有以下几点常常另初学者感到困惑,现举例加以解释:
int b,c,*a;
a=&b;
语句a=&b;并没有改变a的地址,它只是改变了a这个箱子中装的东西。如果你在语句a=&b;的前后用printf("%d",(int)&a);输出a的地址,就会发现它们是一样的。如果后面再来个a=&c;则printf("%d",(int)&a);的输出也一样!

关键是要区分以下几点:
a,表示a的值,即它装的东西,具体到这个例子,a装的是另一个int型变量的地址。如果a不是const类型的,则它装的东西可以改变。比如,这里先装的是b的地址(指针变量是用来装地址的),后改成了c的。
&a,当然就表示a自己的地址了,你可以将a想象成一个箱子,它的地址就是这个箱子的编号。
*a,因为a是一个指针,*a就表示a指向的变量的值,即b或c的值(具体要看a装的是谁的地址,即a指向谁),也即*a=b或*a=c。

现假设a装的是b的地址,那有:
a=&b,即a的值等于b的地址。
*a=b=*(&b),这里*的作用是取出某个地址中的值。因为a的值是b的地址,因此*a取出的是b的值,同理(&b)是b的地址,*(&b)取出的也是b的值!

分析一下a,b,c的内存模型(即它们在内存中是怎样表示的、关系又是怎样的),理解这些就不难了,你边学边体会吧!

❺ C语言怎么写底层.这是什么情况

C语言的内存模型基本上对应了现在von Neumann(冯.诺伊曼)计算机的实际存储模型,很好的达到了对机器的映射,这是C/C++适合做底层开发的主要原因,另外,C语言适合做底层开发还有另外一个原因,那就是C语言对底层操作做了很多的的支持,提供了很多比较底层的功能。

下面结合问题分别进行阐述。

问题:移位操作

在运用移位操作符时,有两个问题必须要清楚:

(1)、在右移操作中,腾空位是填 0 还是符号位;

(2)、什么数可以作移位的位数。

答案与分析:

">>"和"<<"是指将变量中的每一位向右或向左移动, 其通常形式为:

右移: 变量名>>移位的位数

左移: 变量名<<移位的位数

经过移位后, 一端的位被"挤掉",而另一端空出的位以0 填补,在C语言中的移位不是循环移动的。

(1) 第一个问题的答案很简单,但要根据不同的情况而定。如果被移位的是无符号数,则填 0 。如果是有符号数,那么可能填 0 或符号位。如果你想解决右移操作中腾空位的填充问题,就把变量声明为无符号型,这样腾空位会被置 0。

(2) 第二个问题的答案也很简单:如果移动 n 位,那么移位的位数要不小于 0 ,并且一定要小于 n 。这样就不会在一次操作中把所有数据都移走。

比如,如果整型数据占 32 位,n 是一整型数据,则 n << 31 和 n << 0 都合法,而 n << 32 和 n << -1 都不合法。

注意即使腾空位填符号位,有符号整数的右移也不相当与除以 。为了证明这一点,我们可以想一下 -1 >> 1 不可能为 0 。
问题:位段结构

struct RPR_ATD_TLV_HEADER
{
ULONG res1:6;
ULONG type:10;
ULONG res1:6;
ULONG length:10;
};

位段结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。

位结构定义的一般形式为:

struct位结构名{

数据类型 变量名: 整型常数;

数据类型 变量名: 整型常数;

} 位结构变量;

其中: 整型常数必须是非负的整数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。

变量名是选择项, 可以不命名, 这样规定是为了排列需要。

例如: 下面定义了一个位结构。

struct{
unsigned incon: 8; /*incon占用低字节的0~7共8位*/
unsigned txcolor: 4;/*txcolor占用高字节的0~3位共4位*/
unsigned bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/
unsigned blink: 1; /*blink占用高字节的第7位*/
}ch;

位结构烂穗猛成员的访问与结构成员的访问相同。

例如: 访问上例饥桥位结构中的bgcolor成员可写成:

ch.bgcolor

位结构成员可以与其它结构成员一起使用。 按位访问与设置,方便&节省

例如:

struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned state: 1;
unsigned pay: 1;
}workers;'

上例的结构定义了关于一个工从的信息。其中有两个位结构成员, 每个位结构成员只有一位, 因此只占一个字节但保存了两个信息, 该字节中第一位表示工人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。

注意不要超过值限制

问题:字节对齐

我在使用VC编程的过程中,有一次调用DLL中定义的结构时,发觉结构都乱掉了,完全不能读取正确的值,后来发现这是因为DLL和调用程序使用的字节对齐选项不同,那么我想问一下,字节对齐究竟是怎么一回事?

答案与分析:

关于字节对齐:

1、 当不同的族岁结构使用不同的字节对齐定义时,可能导致它们之间交互变得很困难。

2、 在跨CPU进行通信时,可以使用字节对齐来保证唯一性,诸如通讯协议、写驱动程序时候寄存器的结构等。

三种对齐方式:

1、 自然对齐方式(Natural Alignment):与该数据类型的大小相等。

2、 指定对齐方式 :

#pragma pack(8) //指定Align为 8;

#pragma pack() //恢复到原先值

3、 实际对齐方式:

Actual Align = min ( Order Align, Natual Align )

对于复杂数据类型(比如结构等):实际对齐方式是其成员最大的实际对齐方式:

Actual Align = max( Actual align1,2,3,…)

编译器的填充规律:

1、 成员为成员Actual Align的整数倍,在前面加Padding。

成员Actual Align = min( 结构Actual Align,设定对齐方式)

2、 结构为结构Actual Align的整数倍,在后面加Padding.

例子分析:

#pragma pack(8) //指定Align为 8

struct STest1
{
char ch1;
long lo1;
char ch2;
} test1;
#pragma pack()

现在

Align of STest1 = 4 , sizeof STest1 = 12 ( 4 * 3 )
test1在内存中的排列如下( FF 为 padding ):
00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
01 FF FF FF 01 01 01 01 01 FF FF FF
ch1 -- lo1 -- ch2
#pragma pack(2) //指定Align为 2
struct STest2
{
char ch3;
STest1 test;
} test2;
#pragma pack()
现在 Align of STest1 = 2, Align of STest2 = 2 , sizeof STest2 = 14 ( 7 * 2 )

test2在内存中的排列如下:

00 -- -- -- 04 -- -- -- 08 -- -- -- 12 -- -- --
02 FF 01 FF FF FF 01 01 01 01 01 FF FF FF
ch3 ch1 -- lo1 -- ch2

注意事项:

1、 这样一来,编译器无法为特定平台做优化,如果效率非常重要,就尽量不要使用#pragma pack,如果必须使用,也最好仅在需要的地方进行设置。

2、 需要加pack的地方一定要在定义结构的头文件中加,不要依赖命令行选项,因为如果很多人使用该头文件,并不是每个人都知道应该pack。这特别表现在为别人开发库文件时,如果一个库函数使用了struct作为其参数,当调用者与库文件开发者使用不同的pack时,就会造成错误,而且该类错误很不好查。

3、 在VC及BC提供的头文件中,除了能正好对齐在四字节上的结构外,都加了pack,否则我们编的Windows程序哪一个也不会正常运行。

4、 在 #pragma pack(n) 后一定不要include其他头文件,若包含的头文件中改变了align值,将产生非预期结果。

5、 不要多人同时定义一个数据结构。这样可以保证一致的pack值。 问题:按位运算符

C语言和其它高级语言不同的是它完全支持按位运算符。这与汇编语言的位操作有些相似。 C中按位运算符列出如下:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

操作符 作用

————————————————————————————

& 位逻辑与

位逻辑或

^ 位逻辑异或

- 位逻辑反

>> 右移

<< 左移

━━━━━━━━━━━━━━━━━━━━━━━━━━━━

注意:

1、 按位运算是对字节或字中的实际位进行检测、设置或移位, 它只适用于字符型和整数型变量以及它们的变体, 对其它数据类型不适用。

2、 关系运算和逻辑运算表达式的结果只能是1或0。 而按位运算的结果可以取0或1以外的值。 要注意区别按位运算符和逻辑运算符的不同, 例如, 若x=7, 则x&&8 的值为真(两个非零值相与仍为非零), 而x&8的值为0。

3、 与 ,&与&&,~与! 的关系

&、 和 ~ 操作符把它们的操作数当作一个为序列,按位单独进行操作。比如:10 & 12 = 8,这是因为"&"操作符把 10 和 12 当作二进制描述 1010 和 1100 ,所以只有当两个操作数的相同位同时为 1 时,产生的结果中相应位才为 1 。同理,10 12 = 14 ( 1110 ),通过补码运算,~10 = -11 ( 11...110101 )。<以多少为一个位序列> &&、 和!操作符把它们的操作数当作"真"或"假",并且用 0 代表"假",任何非 0 值被认为是"真"。它们返回 1 代表"真",0 代表"假",对于"&&"和""操作符,如果左侧的操作数的值就可以决定表达式的值,它们根本就不去计算右侧的操作数。所以,!10 是 0 ,因为 10 非 0 ;10 && 12 是 1 ,因为 10 和 12 均非 0 ;10 12也是 1 ,因为 10 非 0 。并且,在最后一个表达式中,12 根本就没被计算,在表达式 10 f( ) 中也是如此。

❻ c语言指针在什么情况下需要malloc,什么时候可以不这样,(被这个问题搞晕了)

  1. C语言的内存模型包含有栈和堆两部分。

  2. 栈存放的是函数执行时答圆的变燃尺量等,这部分变量由系统自动管理,比如 int a;等,这些变量在函数体结束时自动收回。

  3. 堆存放的是由用户自己手动管理的变量,这些变量是的由malloc函数建立,由 free函数释放。不皮举高会在函数体结束时自动收回。

❼ C语言和C++的内存模型一样吗

堆栈是一样的。这些细节编程的人敏岁不用管。new出来的空间是自动清袜管理的,编桥正睁程者不用操心。malloc是编程人自己直接管理空间的,你索要了空间,最后要归还。因为系统不知道你什么时候不需要你申请的空间了。

热点内容
入门c语言设计 发布:2025-05-17 12:08:31 浏览:40
c3算法 发布:2025-05-17 12:04:19 浏览:364
phprecv 发布:2025-05-17 11:55:00 浏览:610
福建时钟监控网关服务器云主机 发布:2025-05-17 11:54:28 浏览:248
c数据库压缩 发布:2025-05-17 11:39:22 浏览:960
安卓手机如何连接音响功放 发布:2025-05-17 11:37:48 浏览:959
破解exe加密视频 发布:2025-05-17 11:23:41 浏览:976
我的世界服务器圈太大了怎么办 发布:2025-05-17 11:15:21 浏览:614
便宜的免费云服务器 发布:2025-05-17 11:08:50 浏览:779
中国顶级dhcp解析服务器地址 发布:2025-05-17 11:06:27 浏览:36