C语言复习一(内存对齐、大小端)

内存对齐

  • 为什么要有内存对齐
    • 1、平台原因:不是所有的硬件平台都可以随意的访问某个内存地址上的数据的,有些硬件平台只能访问某些特定地址上的某些特定类型的数据,否则会抛出异常。
    • 2、性能原因:为了访问没有对齐的内存空间时,操作系统可能要对这块内存空间进行两次或者多次内存访问。但是对于对齐的内存空间,操作系统只需要进行一次访问就行
  • 内存对齐的原则
    • 1、第一个成员在结构体偏移量为0的地址处
    • 2、结构体中其他成员要对齐到对齐数的整数倍处,(对齐数:编译器的默认对齐数和成员自身大小的较小值),(VS默认是8字节,Linux默认是4字节)。
    • 3、结构体总大小为最大对齐数的整数倍。(最大对齐数:每个成员的对齐数的最大值)
    • 4、如果结构体中有嵌套,嵌套的结构体对齐到自己最大对齐数的整数倍。整个结构体对齐到最大对齐数(含嵌套的结构体的对齐数)的整数倍。
  • 评价:结构体对齐是拿空间换时间的做法。那么我们要做到既节省空间,系能也要好,那么我们尽量在设计结构体的时候讲占用空间小的成员集中在一起。

大小端

  • 大端:数据的低位存在内存的高地址处,数据的高位存在内存的低地址处。
  • 小端:数据的低位存在内存的低地址处,数据的高位存在内存的高地址处。
  • 一张图让你彻底明白
    在这里插入图片描述

进程的地址空间

在这里插入图片描述

  • 栈的大小:
    windows下:虚拟内存中分配的大小是1M,物理内存中默认
    分配4k,可以被修改。
    linux:linux下用ulimit查看的话是10M,可以被修改。
  • 堆的大小:
    虚拟内存中堆分配的大小是1M,物理内存中是4k,可以被修

柔性数组

  • 柔性数组概念:结构体中的最后一个成员可以是一个不指定大小的数组,成
    为柔性数组。柔性数组前面必须还要有其他成员,可以动态的为柔性数组分配大小。结构体的大小不计入柔性数组的大小。
  • 可以参考一下这篇博文:https://www.cnblogs.com/pluviophile/p/7571410.html

函数指针

  • 指向函数的指针就叫做函数指针。函数指针初始化的时候直接给指针赋值函数名,或者赋值为取地址函数名都是可以的。
  • 用途:
    • 回调函数:将一个函数指针作为参数传递给其他函数。
    • 转移表:实际上是一个函数指针数组,就是一个数组里面放的全部都是函数指针。
  • 我来写一个函数指针
void test()
{
	printf("hello world!\n");
}

void (*pTest)();  // pTest 就是一个函数指针
pTest = test ;
void (*ppTest)() = test;  //也可以在声明的时候就进行初始化
  • 上面的代码 pTest首先与 * 结合,表明他是一个指针,接下来他的返回值是void(没有返回值),他的参数为空,代表不用传参。

程序生成的过程,编译链接过程

  • 编译链接过程分为预处理编译汇编链接四个过程。
  • 预处理:消除注释,替换宏,处理预处理指令,包含库文件
  • 编译:词法分析,语法分析,语义分析,优化处理
  • 汇编:把汇编语言翻译成目标机器指令,生成目标文件
  • 链接:将目标文件链接生成可加载,可执行的目标文件

宏的优缺点

  • 优点:
  1. 提高了程序的可读性,同时也方便进行修改;
  2. 提高程序的运行效率:使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈与入栈操作,减少系统开销,提高运行效率;
  3. 宏是由预处理器处理的,通过字符串操作可以完成很多编译器无法实现的功能。
  • 缺点:
  1. 由于是直接嵌入的,所以代码可能相对多一点;

  2. 嵌套定义过多可能会影响程序的可读性,而且很容易出错;

  3. 对带参的宏而言,由于是直接替换,并不会检查参数是否合法,存在安全隐患。

  4. 预编译语句仅仅是简单的值代替,缺乏类型的检测机制。这样预处理语句就不能享受C++严格的类型检查的好处,从而可能成为引发一系列错误的隐患。

宏与函数的区别:

  • 时间上考虑:
  1. 宏只占编译时间,函数调用则占用运行时间(分配单元,保存现场,值传递,返回),每次执行都要载入,所以执行相对宏会较慢。
  2. 使用宏次数多时,宏展开后源程序很长,因为每展开一次都使程序增长,但是执行起来比较快一点(这也不是绝对的,当有很多宏展开,目标文件很大,执行的时候运行时系统换页频繁,效率就会低下)。而函数调用不使源程序变长。 2. 使用宏次数多时,宏展开后源程序很长,因为每展开一次都使程序增长,但是执行起来比较快一点(这也不是绝对的,当有很多宏展开,目标文件很大,执行的时候运行时系统换页频繁,效率就会低下)。而函数调用不使源程序变长。
  • 安全性考虑:
    3. 函数调用时,先求出实参表达式的值,然后带入形参。而使用带参的宏只是进行简单的字符替换。
    4. 函数调用是在程序运行时处理的,分配临时的内存单元;而宏展开则是在编译时进行的,在展开时并不分配内存单元,不进行值的传递处理,也没有“返回值”的概念。
    5. 对函数中的实参和形参都要定义类型,二者的类型要求一致,如不一致,应进行类型转换;而宏不存在类型问题,宏名无类型,它的参数也无类型,只是一个符号代表,展开时带入指定的字符即可。宏定义时,字符串可以是任何类型的数据。
    6. 宏的定义很容易产生二义性,如:定义#define S(a) (a)(a),代码S(a++),宏展开变成(a++)(a++)这个大家都知道,在不同编译环境下会有不同结果。
  • 结构性考虑:
    7. 调用函数只可得到一个返回值,且有返回类型,而宏没有返回值和返回类型,但是用宏可以设法得到几个结果。
    8. 函数体内有Bug,可以在函数体内打断点调试。如果宏体内有Bug,那么在执行的时候是不能对宏调试的,即不能深入到宏内部。
    9. C++中宏不能访问对象的私有成员,但是成员函数就可以。

宏与内联函数的区别

  • 内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
  • 内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
  • 如何选择使用宏还是函数:
    以下情况可以选择宏,其他情况最好选用函数
    1. 一般来说,用宏来代表简短的表达式比较合适。
    2. 在考虑效率的时候,可以考虑使用宏,或者内联函数。
    3. 在头文件保护(防止重复包含编译),条件编译中的#ifdef,#if defined以及assert的实现。

#ifndef和#pragma有什么区别

  • #pragma once是微软独有,不支持跨平台,且它只要发现头文件被包含就不会打开头文件。
  • #ifndef是要每次打开文件判断头文件是否被包含的。所以总的说#pragma编译的效率更快。

自己实现一个 atoi 函数

#include<iostream>
#include<string>
#include<vector>

bool status = true; //状态,出现错误一律是false,正确就是true

int my_atoi(std::string str)
{
	//如果遇到字符不是数字字符、或者字符前有空格都返回错误
	//如果输入的字符串中没有数字,也给他返回错误
	//如果要转换的字符串过大,可能会溢出,溢出的话也给他返回错误
	int flag = 1; //标记正负,  -1 代表负数  1 代表正数
	int i = 0; //遍历str的下标
	std::vector<int> vec;
	if (str.empty())
	{
		status = false;
		return 0;
	}
	if (!((str[0] >= '0' && str[0] <= '9') || (str[0] == '+') || (str[0] == '-')))
	{
		status = false;
		return 0;
	}
	if (str[0] == '+')
	{
		flag = 1;
		i = 1;
	}
	else if (str[0] == '-')
	{
		flag = -1;
		i = 1;
	}
	for (; i < str.size(); ++i)
	{
		if (!(str[i] >= '0' && str[i] <= '9'))
		{
			status = false;
			return 0;
		}
		else
		{
			vec.push_back(str[i] - '0');
		}
	}
	int len = vec.size() - 1;
	int num = 1;
	long int res = 0;
	for (int i = len; i >= 0; --i)
	{
		res += vec[i] * num;
		num *= 10;
	}
	res *= flag;
	if (res >= INT_MAX || res <= INT_MIN)
	{
		status = false;
		return 0;
	}
	return (int)res;
}


int main()
{
	std::string str;
	std::cin >> str;
	int res = my_atoi(str);
	if (status == false)
	{
		std::cout << "输出有误,请重新输入" << std::endl;
		return 0;
	}
	std::cout << res << std::endl;
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40421919/article/details/88034042