当前位置: 代码迷 >> 综合 >> 内存分配,sizeof,指针,预编译,结构体和类,位操作,函数,数组,变量,字符串,编译
  详细解决方案

内存分配,sizeof,指针,预编译,结构体和类,位操作,函数,数组,变量,字符串,编译

热度:7   发布时间:2023-12-05 21:59:40.0

内存分配的形式

  • 1.由符号起始的区块BSS(block started by symbol):存放未初始化的全局数据和静态数据。资源由系统自动释放
  • 2.数据段:存放已初始化的全局变量
  • 3.代码段:也叫文本段,通常指用来存放程序执行代码的一块内存区域
  • 4.堆:malloc和new等方式分配的内存
  • 5.栈:用于存放程序临时创建的局部变量。一般包括{}内的定义的变量(static除外)

  • 堆,栈,自由存储区(new),全局/静态存储区,常量存储区
char  a[] = "asdfgh";//分配好空间,然后将字符串放入
char *p = "asdfgh"; //指针,没有空间,字符串在常量区,p在栈上

栈空间的最大值

  • 栈顶地址和栈的最大容量一般都是系统预先设定好的:windows栈一般为2MB,堆一般小于2GB
  • 栈空间小,但是速度快,一般是连续的。由高地址向低地址
  • 堆的本质其实是链表(和数据结构的堆不是同一个东西),堆空间一般是不连续的。由低向高

缓冲区溢出

  • 意思就是你申请的变量空间,如数组空间,访问超界,从而导致覆盖掉合法的数据
int main()
{int i = 0;int a[]={1,2,3,4,5};for(i=0; i<=5; i++){a[i] = 0;}cout <<"complete";return 0;
}
//程序会无限循环
//由下表可知,a[5]对应的就是i的存储地址,因此i=0;

变量i和数组a栈中存储的方式:

从下向上地址递增
i
a[4]
a[3]
a[2]
a[1]
a[0]

sizeof和strlen

  • sizeof是关键字,也是运算符。可以用类型作为参数,如:sizeof(int),就是指的,在该系统中,int占用几个字节。计算是在编译时进行的,因此,可以使用sizeof(x)来初始化数组
  • strlen()是函数,用来计算字符串的长度,遇到\0停止;其参数只能是char*。是在运行时计算
数组名可以当做指针使用,但是他又不是指针,因为他包含了数组的长度信息
当数组名作为参数进行传递时,就会退化成指针,指针是不包含长度信息的
因此,我们一般传递指针的话,需要也传递一个长度
char a[10];
char *p = a;
size_t A = sizeof(a);//数组长度,字节数
size_t B = sizeof(a[0]);//数组中的元素的类型长度,字节数
size_t len = sizeof(a)/ sizeof(a[0]);//数组长度//元素个数
size_t P1 = sizeof(p);//指针长度,占用四个字节
size_t P2 = sizeof(*p);//数组内第一个数据,char的长度,1
cout<<A<<P1<<P2;//10,4,1

对引用使用sizeof得到的是该变量(对象)的大小。对指针,则是指针的大小4,64位系统则是8


对于结构体而言,为什么sizeof返回的值一般大于期望值

  • 一般而言,struct的sizeof是所有成员对齐后长度相加
  • 而,union的sizeof是去最大的成员长度
  • 因为需要对齐:
  • 结构体变量的首地址能够被其最宽基本类型成员的大小所整除
  • 每个成员对于结构体的首地址偏移量都是该成员大小的整数倍(即每个成员都会默认,里面存的都是自己的类型,因此,即便前面存的是个char,而现在成员是int,则存int的时候,也会认为前面存的是一个int,因此就会在char后面添加三个字节,凑够四字节)
  • 结构体总大小为最宽成员的整数倍,不够则添加(就是,如果先存了一个int,后存了三个char,那么,虽然占用了7个字节,但是为了是最大宽度int的整数倍,那么我们就要补充到8字节)

指针和数字相加

  • 根据数据类型,指针+1,就是加了该指针指向的数据类型的长度

空指针和野指针

  • 野指针

  • 野指针是指向不可用内存的指针,一般主要有以下:
  • 1指针创建时,未被初始化!,随机分配的地址,就是野指针
  • 2当指针指向的对象被删除或析构,指针未被NULL,那么指针就是野指针
  • 空指针就是空指针


预编译


#include<>和#include""

  • <>是编译器先从标准库路径开始搜索头文件,使得系统文件调用较快
  • “”是编译器先从用户路径开始搜索文件,使得用户自定义文件较快

宏定义

  • 缺点
  • 不会进行类型检查,只是简单的替换
  • 由于简单的替换,也就会导致错误
#define MAX_NUM 1000+1
int Temp = MAX_NUM*10;
//Temp的值是1000+1*10=1010,而不是我们想要的1001*10=10010//还比如
#define sqr(x) x*x
a = sqr(b+c);//b+c*b+c;//错误//通过添加括号,就能解决上面的问题
#define MAX_NUM (1000+1)

含参数的宏和函数有什么区别

  • 宏是在编译的时候进行的替换,函数是程序运行的时候
  • 宏的替换,会导致程序变长,函数不会
  • 宏在替换时,没有类型的概念,没有传值的处理,也没有返回值的概念,只是简单的替换。参数会在运行时确定

如何判断一个数是有符号数还是无符号数

  • 取反:正数首位是0.负数是1。无符号数就不会受此影响
  • 无符号数和有符号数相见的结果为无符号数
  • 直接暴力将首位置1,如果是负的就是有符号的,如果还是正的就是无符号的

typedef 和 define 的区别

  • 前者是关键字,后者是预处理指令
  • 前者有作用域 ,后者没有
  • 对于指针的操作不同
//t同样的对于int *
typedef int* A1;
#define A2 in*;
A2 p1,p2  ;
int * p1,p2;//define//指的是一个指针和一个整形
A1 p1, p2;
int *p1, *p2;//typedef//两个指针

宏定义和内联函数的区别

  • 宏定义是在预处理阶段进行替换,内联是在编译阶段插入代码
  • 前者没有类型检查,内联有

内联和普通函数的区别?

  • 内联是在使用处直接展开
  • 普通的,则需要调用。会有时间开销

内联是否可以是递归函数?

  • 可以,但是会导致失去内联的功能,因为编译器不知道递归的深度

define和const

  • 前者没有数据类型,前者不支持调试
  • 前者是止于编译期,不分配空间。后者存在于程序的数据段,并分配了空间

C 和C++的struct的区别

  • C的就是狭义上的struct
  • C++上的,基本等同于类

struct 和class

  • 前者继承是默认public的,数据也是默认public的
  • 后者则都是默认private的

如何快速求一个数的7倍

  • (a<<3) - a
  • 三位就是八倍,再减去自身,就是7倍

位操作求平均数

  • 有时,x和y可能没有溢出,但是两者的和会溢出,此时怎么求平均呢
(x&y) + ((x^y)>>1)
//意思就是,两者相同的部分,不变,因为求和除2之后,对应位还是不变
//两者不同的部分,相加(异或就是相加了),在除以2
//两者只和就是平均数了

位操作求绝对值

//求x的绝对值
int y;
y= x>>31;
return (x^y) - y;

函数指针与指针函数

  • 重点是后面的词,决定了他是什么
  • 函数指针:是一个指针,指向函数
  • 指针函数:是一个函数,返回的指针
  • 数组指针:是一个指针,指向数组
  • 指针数组:是一个数组,元素是指针
  • 常量指针:是一个指针,指向一个常量
  • 指针常量:是一个常量,常量是指针

可重入函数

  • 可以被中断的函数,且不会造成任何错误
  • 可重入函数不能调用不可重入函数,不然其也会变成不可重入函数
  • 主要用于多线程
  • 满足一下条件,多数是不可重入的:
  • 1函数体使用了静态数据节后或全局变量
  • 2调用了malloc或者free
  • 3调用了标准的I/O函数

  • 函数


函数指针和指针函数

  • 指针函数指的是返回值为指针类型的函数
  • 函数指针是一个指向函数的指针
int *func(参数);
int (*p)(参数);
p=func;//指的是将函数的首地址赋值给指针int (*func[]) (int a, intb);
//这是一个函数
//这是一个函数指针
//这是一个指向函数指针的数组
//意思就是func数组中的成员是函数指针

数组指针与指针数组

  • 看后面的词是什么,那就是什么
  • 数组指针是一个指针,指向一个数组
  • 指针数组是一个数组,内元素是指针

函数模版和模板函数

  • 函数模板是一个模版,不是一个具体的函数
  • 模版函数是函数模板实例化后的函数

类模板和模板类

  • 同上

指针常量和常量指针

  • 指针常量: int * const p
  • 常量指针: int const *p;

C++传递参数的方式

  • 值传递
  • 指针传递
  • 引用传递
  • 全局变量传递

数组


二维数组

int a[2][2] = {
   {1},{2,3}};
//a[0][1]的值是多少?
//1,0
//2,3
//所以,是0//第一维可以不指出,但是第二维必须指出
int m[][3] = {1,2,3,4,5,6,7,8,9};

例题分析
//对于数组a[3][4],下面那一个可以表示a[1][1]?
*(a[1]+1);// 正确
*(&a[1][1]);//根据优先级[][]高,则就是对a[1][1]先取地址,又解引用。正确
(*(a+1))[1];//*(a+1),其实就是a[1],因此就是a[1][1];
*(a+5);//同上。就是a[5]。越界,错误。
*(*(a+1)+1);//正确

例题分析

int a[5] = {1,2,3,4,5};
int *ptr = (int*)(&a+1);
*(a+1);//上面说了,*(a+1)就是a[1],因为是一维的,即,此处就是2
*(ptr-1);//&a是一个指向int* [5]的指针,sizeof(&a)是一个指针的长度,4字节。但是&a+1的值取决于&a指向的类型
&a+1 = &a+sizeof(int)*5 = &a+20 = a[5];
*(ptr-1) = a[4];
sizeof(a);//4*5=20。//就是数组占据的空间,五个int=4*5
sizeof(ptr);//4,就是指针的占据的空间,4字节

代码不全,记得补充

所以,总结就是

a是首元素的地址,a+1就是下个元素的地址;&a是数组的地址,&a+1就是下个数组的地址

*(a+x),指的是数组的第一维的变化a[x][]。如果是一维,则就直接是a[x];

(&a+x),指的是数组长度的变化?a+xsizeof(a)?


行存储和列存储,行存储效率高

//对于一个二维数组a[M][N]
for (int i = 0; i < M; i++)for (int j = 0; j < N; j++){}for (int j = 0; j < M; j++)for (int i = 0; i < N; i++){}
//因为C++是行存储策略,所以行存储效率高,即(1)效率高

变量


静态变量和全局变量的区别

两者都在静态存储区
  • 静态全局变量:如果有多个文件,则他只作同于定义它的文件。不同的文件,可以定义同名的静态全局变量
  • 静态局部变量:只对于定义它的函数可见,虽然直到程序销毁时才被删除,但是其他函数不可见
  • 全局变量:多个文件夹,不是定义它的文件,只要使用了extern,对于谁都可见。如果局部变量和全局重名,则在重名的局部内使用局部变量,其他时,使用全局
  • 局部变量变为静态变量:改变了变量的存储位置,还有生存期
  • 全局变量改为静态变量:二者都是在全局/静态变量存储区,改变的是作用域

局部变量需要避讳全局变量吗

  • 局部变量存储在栈,全局存储在静态存储区
  • 不需要
  • 在局部,局部变量会屏蔽全局变量

如何建立和理解复杂的声明

int a;
int *a;
int **a;
int a[10];
int * a[10];//存放有十个指针的数组
int (*a) [10];//一个指向十个元素的数组的指针
int (*a) (int);//一个函数指针,参数是int,返回值是int
int (* a[10]) (int);//,一个数组内,存放的是函数指针,该函数参数是int,返回值是int
float *a(), (*b)();//a是一个返回float指针的函数。b是一个指向一个返回float的函数的指针

例题

//1
void *( * (*fp1)(int) )[10];
//化简成两个
p = (*fp1)(int); //fp1一个参数为int的函数指针
void *(*p) [10];//p一个指向10个元素的数组的指针,数组元素类型为void*
//综合:fp1就是一个,函数指针,他的参数是int,返回值是一个指针。该返回的指针指向一个数组,数组有10个元素,类型为void*//2
float(*(*fp2)(int, int, int))(int);
//fp2是一个函数指针,函数参数为三个int,返回值是一个函数的指针。该指针指向一个参数为int,返回值为float的函数//3
int(*(*fp3)())[10]();
//fp3是一个函数指针,该函数无参数,返回值是一个指针,该指针指向一个数组,数组元素是函数指针,函数无参数,返回int

变量定义与声明的区别

  • 定义的时候,给变量分配存储空间,一个程序中,只能由一个定义
  • 声明,是向程序表明变量的类型和名字
  • 定义也是声明
int a;//这是一个定义,也是声明
externa int a;//这是一个声明,不是定义

不使用第三方变量,交换两个变量的值

算术法

a = a + b;
b = a - b;
a = a - b;

异或法

a = a ^ b;
b = a ^ b;
a = a ^ b;

类型转换

  • 把参与运算的表达式隐式的转换为占空间最大的那种类型
  • 无符号数和有符号数进行计算的时候,有符号数就会转换成无符号数。
ulong u = 1;
char c = 2;
int i = u+c;
//或者int i = c+u;和上面一样
//u+c的时候,类型还是ulong
//然后再ulong转换为int

字符串


内存重叠,move函数的问题

  • 假设,长度都是10,源是a,目地是b,用来表示占据的空间
  • 重叠:aaaabbbbbbbbbb
  • 这时,如果从左向右拷贝,就会导致,源a的后半部分,在拷贝的时候,已经被b的新内容覆盖了,出错
  • 我们就需要从右向左移动,即,从尾向首,这样虽然源a的值被覆盖,但是覆盖的值都已经移动到b了
  • 重叠:bbbbaaaaaaaaaa
  • 这时如果再从右向左,就会导致a的前边还未移动,就已经被覆盖了
  • 需要从左向右拷贝

如何自己编写strcpy(),将一个字符串复制到另一个字符串

char* strcpy(char*strDest, const char* strSrc)
{assert((strDest != NULL) && (strSrc != NULL));if (strDest == strSrc)return strDest;char *address = strDest;//用于返回,因为strDest会被我们++while ((*strDest++ = *strSrc++) != '\0');return address;
}
// 看完了?但是这是错的
  • 1.首先,没考虑,对于原较短,而目的较长的情况
  • 如,源是“hi”,目的是“hello”。
  • 那么按照刚才编写的,结果就是“hi\0lo”,而不是我们想要的“hi”
  • 2. 越界
  • 如一个长度11的源字符串,拷贝到一个长度10的目的字符串
  • 3.空间重叠

  • 如何自己编写函数把数字变成字符串

  • 下面的程序进攻参考学习,考虑的情况较少
int mystoi(char* str)
{assert(str != NULL);char *p = str;while(*p == ' ')//去除前面的空格p++;int sign = (*p == '-') ? -1 : 1;//正负int result = 0;while (*p >= '0'&& *p <= '9'){result = result * 10 + (*p - '0');p++;}while (*p == ' ')//去除结尾的空格p++;if (*p != '\0'){cout << "输入非法";return -1;}return sign*result;
}

编译


一个程序从开始运行到结束的完整过程(四个过程)

1、预处理:条件编译,头文件包含,宏替换的处理,生成.i文件。

2、编译:将预处理后的文件转换成汇编语言,生成.s文件

3、汇编:汇编变为目标代码(机器代码)生成.o的文件

4、链接:连接目标代码,生成可执行程序


编译和链接的区别

  • 通常,想要将源代码编程可执行的程序,需要三步:编译,链接,载入
  • 编译
  • 词法,语法,语义分析后,编译成若干个目标模块,内容就是二进制代码,即机器语言
  • 链接
  • 将编译后的各个块以及他们所需要的库函数链接在一起,形成一个完整的载入模型
  • 主要解决模块间的相互引用问题。分为地址和空间分配,符号解析和重定位几个步骤。
  • 载入
  • 由载入程序将载入模块载入内存

编译型语言和解释型语言

  • 编译型语言
  • 是指在源程序执行前,就将程序源代码翻译成目标代码
  • 因此,其目标程序可以脱离其语言环境独立执行,使用方便,效率较高
  • 但是应用程序一旦需要修改,就必须先修改源代码,再重新编译生成目标文件
  • 将目标程序直接发给用户,不仅便于执行,也防盗用
  • C、C++、Pascal、Ada等
  • 解释型语言
  • 翻译器并不产生目标机器代码,而是产生易于执行的中间代码
  • 中间代码的解释是由软件执支持的,不能直接使用硬件
  • 软件解释器通常会导致执行效率较低
  • 执行一次就翻译一次,效率低下
  • Tcl、Perl、Ruby、JS
  • 需要注意的是,JAVA是一类特殊的编程语言
  • 塔也要编译,但是不是输出的机器语言,而是编译为字节码
  • 然后再JAVA虚拟机上以解释方式执行字节码

两段代码共存于一个文件,如何有选择的编译其中一部分

  • 在源码中使用条件编译语句,然后再程序文件中定义宏的形式来选择需要的编译代码
  • 在源码中使用条件编译语句,然后在编译命令的命令中加入宏定义命令来实现选择编译

  相关解决方案