#include <stdio.h>
#include "string.h"
#include "malloc.h"

void Swap(int a,int b)
{
    int temp;
    temp=a;
    a=b;
    b=temp;
}

int Get_Int(int a)
{
    +a;
    return i;
}

char* Get_Memory0()
{
    );
    strcpy_s(p,"hello world");
    return p;
}

char* Get_Memory1()
{
    char* p="hello world";
    return p;
}

char* Get_Memory2()
{
    char p[]="hello world";
    return p;
}

void main()
{
    ,y=;
    Swap(x,y);
    int z=x-y;
    printf("z=%d\n",z);

    z=Get_Int(z);
    printf("z=%d\n",z);

    char* c0=Get_Memory0();
    printf("c0=%s\n",c0);

    const char* c1=Get_Memory1();
    printf("c1=%s\n",c1);

    char* const c2=Get_Memory2();
    printf("c2=%s\n",c2);
}

面试题,上面的程序执行结果是什么?

基础知识

内存存储:

在C/C++中,通常可以把内存理解成4个分区:栈、堆、全局/静态存储区和常量存储区。

(1)栈:通常是用那些在编译期间就能确定其存储大小的变量的存储区,用于在函数作用域内创建、在离开作用域后自动销毁的变量的存储区。通常是局部变量、函数参数等的存储区。它的存储空间是连续的,两个紧挨着的定义的局部变量,它们的存储空间是紧挨着的。栈的大小是有限的,通常Visual C++编译器默认栈的大小是1M,所以不要定义int a[1000000]这样的超大数组。

(2)堆:通常是用于那些在编译期间不能确定存储大小的变量存储区,它的存储空间是不连续的,一般同malloc(或new)函数来分配内存块,并且需要要free(或delete)释放内存。如果程序员没有释放掉,那么就会出现常说的内存泄漏问题。需要注意的是,两个紧挨定义的指针变量,所指向的malloc出来的内存并不一定是紧挨着的。另外需要注意的一点是,堆的大小几乎是不受限制的,理论上每个程序最大可达4GB。

(3)全局/静态存储区:和“栈”一样,通常是用于那些在编译期间就能确定存储大小的变量的存储区,但它用于的是在整个程序运行期间都可见的全局变量和静态变量。

(4)常量存储区:和“全局/静态存储区”一样,通常是用于那些在编译期间就能确定存储大小的常量存储区,并且在程序运行期间,存储区内的常量也是全局可见的。这是一块比较特殊的存储区,它们里面放的是常量,不允许被修改。

程序解答:

(1)Swap函数:我们知道,所有函数都会在运行时从程序“栈”上得到分配给它的一块存储区(这里的“栈”就是前两面讲解内存存储时提到的“栈”,第个运行程序都拥有自己的运行时“栈”)。这块“栈”上的函数存储区随函数的开始面开始,随着函数的结束而结束。函数结束后,这块存储区就会自动释放,以供程序的其他用途使用。系统在函数运行时会为函数的每一个参数都提供存储区,参数在存储区中的存储长度由自身的类型决定。参数传递,就是系统用函数实参初始化函数参数存储区的过程。

函数的参数有传值(传指针)和传址(传引用)两种。所谓参会传值,就是实参的值复制到函数运行时分配给函数的参数存储区中。参数在传值时,函数不会访问当前调用的实参,函数处理的是实参在本地的拷贝,这些拷贝被存储在函数的“栈”中,所以这些拷贝值的改变不会影响实参的值。

答案:

z=1

(2)问题:返回值i是一个局部变量,函数的返回参数怎么能是局部变量呢?局部变量在离开作用域后就自动被销毁了,还怎么能返回值给调用都呢?

解答:函数的返回值有传值和传址两种。int Get_Int(int a)属于返回值是传值的函数,这就意味着函数int Get_Int(int a)会在函数返回处产生一个临时对象,用于存放局部变量i的值的一份拷贝(变量i的右值的拷贝),临时对象是没有名称的,这份没有名称的对象的值(右值)会存储在调用者的“栈”中。所以当i作为局部变量离开作用域后,虽然被销毁了,但它的拷贝仍然存在,并在函数返回时作为“右值”赋给“左值z”。

答案:

z=2

(3)此处考查对“堆”的理解:函数返回的是指向“堆”内存的指针。程序中malloc()是用于分配“堆”内存的库函数,而对于“堆”内存,只要程序中没有调用free()库函数去释放掉该“堆”内存,那么在程序运行期间,malloc()库函数分配的“堆”内存将一直存在。

char* p=(char*)malloc(sizeof(char)*20)表示分配一块“堆”内存并使得变量p指向这块“堆”内存。

strcpy(p,"hello world")表示往p指向的“堆”内存中复制字符串hello world。

return p,变量p的左值是一个局部变量指针,存储于函数栈上,右值 是“堆”的地址(“堆”的值是hello world)。

函数char* Get_Memory0()属于返回值是传指针的函数,这就意味着函数char* Get_Memory0()会在函数返回处产生一个对返回变量p的“左值”的拷贝,也就是在“左值”的拷贝中存储了指向“堆”的地址。作为局部变量的 p,在离开函数作用域的时候虽然被销毁了,但函数返回值珠“左值”的拷贝是存在的,该拷贝存储了指向“堆“的地址,而而该“堆”的值是hello world。

答案:

c1=hello world

(4)这里考查对函数返回值和指针的理解:变量分为左值和右值,在char* p="hello world"中,左值是局部变量指针p,存储于函数栈上,右值是字符串常量hello world,存储于常量存储区。

char* Get_Memory1()属于返回值是传指针的函数,这就意味着函数char* Get_Memory1()会在函数返回处产生一个对返回对象的“左值”的拷贝,也就是在“左值”拷贝中存储了指向字符串常量hello world的地址。作为局部变量的p,在离开函数作用域的时候虽然被销毁了,但函数 返回的“左值”拷贝仍然存储了指向常量存储区的字符串常量hello world的地址。

答案:

c1=hello world

(5)和问题(4)有些相似,考查的也是对函数返回值和指针的理解:返回值是传指针,会在函数返回时产生“左值”拷贝。可以看到,p[]不是指针,是一个数组变量。编译器根据右值“hello world”的长度(12个字符)在编译期间在函数的栈上为数组分配大小为12个字符的内存存储区,其值是“hello world”。

通常,变量的意义在于,它给一块内存存储区提供名字,方便程序对这块内存进行读写。变量包含两个值:左值和右值。左值是内存存储区的名字,右值是存放存储区中的值。从程序中可以看到,函数返回的“左值”拷贝 指向的是局部变量数组p[12]的首地址。当局部数组p[12]作用域后会被自动销毁。这时,函数返回的“左值”拷贝指向的是一个被销毁的局部变量地址。

答案:

warning C4172: returning address of local variable or temporary

c2=未知

C/C++内存存储问题的更多相关文章

  1. Redis内存存储结构分析

    1 Redis 内存存储结构 本文是基于 Redis-v2.2.4 版本进行分析. 1.1 Redis 内存存储总体结构 Redis 是支持多key-value数据库(表)的,并用 RedisDb 来 ...

  2. C/C++内存存储

    #include <stdio.h> #include "string.h" #include "malloc.h" void Swap(int a ...

  3. memcached全面剖析–2. 理解memcached的内存存储

    Slab Allocation机制:整理内存以便重复使用 最近的memcached默认情况下采用了名为Slab Allocator的机制分配.管理内存. 在该机制出现以前,内存的分配是通过对所有记录简 ...

  4. 1.C#基础学习笔记3---C#字符串(转义符和内存存储无关)

    技术qq交流群:JavaDream:251572072  教程下载,在线交流:创梦IT社区:www.credream.com ------------------------------------- ...

  5. 【解惑】剖析float型的内存存储和精度丢失问题

    问题提出:12.0f-11.9f=0.10000038,"减不尽"为什么? 现在我们就详细剖析一下浮点型运算为什么会造成精度丢失? 1.小数的二进制表示问题 首先我们要搞清楚下面两 ...

  6. Go Web:数据存储(1)——内存存储

    数据可以存储在内存中.文件中.按二进制序列化存储的文件中.数据库中等. 1.内存存储 2.CSV文件存储 3.gob序列化存储 内存存储 将数据存储到内存中.此处所指的内存是指应用程序自身的内存空间( ...

  7. memcached全面剖析--2.理解memcached的内存存储

    下面是<memcached全面剖析>的第二部分. 发表日:2008/7/9 作者:前坂徹(Toru Maesaka) 原文链接:http://gihyo.jp/dev/feature/01 ...

  8. spark 源码分析之十六 -- Spark内存存储剖析

    上篇spark 源码分析之十五 -- Spark内存管理剖析 讲解了Spark的内存管理机制,主要是MemoryManager的内容.跟Spark的内存管理机制最密切相关的就是内存存储,本篇文章主要介 ...

  9. 32 位bitmap 内存存储 顺序 bgra 前3位 与23位一致。 都是 bgr 呵呵 与rgb 相反

    32 位bitmap     内存存储 顺序   bgra       前3位 与23位一致.   都是 bgr  呵呵 与rgb 相反

随机推荐

  1. 【剑指offer】面试题36:数组中的逆序对

    题目: 在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对.输入一个数组,求出这个数组中的逆序对的总数. 思路: 归并排序的合并过程.主要是考虑合并两个有序序列时,计算逆序 ...

  2. Android 开机启动通知

    效果图: 学习: 1.静态注册实现开机启动 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLET ...

  3. java_重写与重载的区别

    重写与重载的区别 重载(Overloading)和重写(Overriding)是Java中两个比较重要的概念.但是对于新手来说也比较容易混淆.本文通过两个简单的例子说明了他们之间的区别. 定义 重载 ...

  4. zoj 3547 The Boss on Mars

    需要用到概率论的容斥定理以及计算1 ^ 4 + 2 ^ 4 + ……+ n ^ 4的计算公式1^4+2^4+……+n^4=n(n+1)(2n+1)(3n^2+3n-1)/30 #pragma comm ...

  5. 【足迹C++primer】46、动态存储类

    动态存储类 StrVec Class Design StrVec Class Definition class StrVec { public: //构造函数 StrVec():elements(nu ...

  6. QiniuUpload- 一个方便用七牛做图床然后插入markdown的小工具

    最近一段时间有用markdown做笔记,其他都好,但是markdown插入图片挺麻烦的,特别是想截图之后直接插入的时候.需要首先把图片保存了,然后还要上传到一个地方生成链接才能插入.如果有个工具可以直 ...

  7. ubuntu16.04 server安装小记

    由于本人有一台闲置的thinkpad电脑,所以打算在上边安装一个ubuntu16.04 server版本,其中遇到主要问题,做一下记录: 安装过程中出现“ubuntu16.04 server64 bu ...

  8. 《第一行代码》学习笔记14-UI(3)

    1. (1)所有控件都是直接或间接继承自View,所用的所有布局都是直接或间接继承自ViewGroup的. (2)View是Android中一种最基本的UI组件,可以在屏幕上绘制一块矩形区域,并能响应 ...

  9. Oracle decode函数 除数为零

    decode (expression, search_1, result_1)如果 expression结果=search_1结果,则返回result_1,类似 if elsedecode (expr ...

  10. [总结] Stack: Java V.S. C++

    小结一下Stack 的主要API操作. 在c++ 和 java 中,stack 的操作几乎相同,只有查询栈顶元素一项操作的名称不同 (top() v.s. peek()) . 此外,在构造函数中,Ja ...