| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> 01计算机C语言 -> 正文阅读 |
|
[C++知识库]01计算机C语言 |
C语言****************************************************************************************************************** 判断大小端: 1、联合体判断:利用union结构体从低地址开始存,且同一时间只有一个成员变量占有内存。
2、使用指针: 将int类型强制转换成char fread 指针位置 从给定输入流stream读取最多count个对象到数组buffer中(相当于对每个对象调用size次fgetc),把buffer当作unsigned char数组并顺序保存结果。流的文件位置指示器前进读取的字节数。 堆栈: 【C】malloc动态分配内存和free释放_朱又炖粉条的博客-CSDN博客_malloc方式分配的堆空间由系统自动释放 2、堆栈缓存方式区别 栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放。 堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定(并不是一旦成为孤儿对象就能被回收)。所以调用这些对象的速度要相对来得低一些。 3、堆栈数据结构区别 堆(数据结构):堆可以被看成是一棵树,如:堆排序。 栈(数据结构):一种先进后出的数据结构。 malloc动态分配的内存在堆区,其空间并不连续。函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。 什么是堆:堆是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。 什么是栈:栈是线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立。每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。 栈是由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。 堆一般由程序员分配释放,若不释放,程序结束时可能由OS回收。注意这里说是可能,并非一定。所以我想再强调一次,记得要释放! 注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。 所以,举个例子,如果你在函数上面定义了一个指针变量,然后在这个函数里申请了一块内存让指针指向它。实际上,这个指针的地址是在栈上,但是它所指向的内容却是在堆上面的!这一点要注意!所以,再想想,在一个函数里申请了空间后,比如说下面这个函数:
就这个例子,千万不要认为函数返回,函数所在的栈被销毁指针也跟着销毁,申请的内存也就一样跟着销毁了!这绝对是错误的!因为申请的内存在堆上,而函数所在的栈被销毁跟堆完全没有啥关系。所以,还是那句话:记得释放! 1. 函数原型及说明: void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。 关于分配失败的原因,应该有多种,比如说空间不足就是一种。 void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。 2. 关于函数使用需要注意的地方 A、申请了内存空间后,必须检查是否分配成功。 B、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。 如何表示这一块堆内存已经被占用,避免别的程序又来分配我的空间呢。用标志位来标记,所以加上了标志位之后n=10时堆中开辟的空间肯定不止40字节。 注意 free释放的是指针指向的内存,不是指针。指针只是一个变量,只有程序结束时才被销毁。释放了内存空间后,原来指向这块空间的指针还是存在。? 当空间释放(free)时,标志位状态就会发生变化,此时又允许其他程序分配我原本的内存空间。此时指针变为无效指针,必须把无效指针变为空。 否则失效指针仍可对内存操作,但有可能这一块内存被分配给别的程序,就会使无效指针修改别人的数据。麻烦大了。 C、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。 D、malloc申请的指针不要动它。可以动它的值,但自身不能动。 这里涉及malloc()以及free()的机制 “大多数实现所分配的存储空间比所要求的要稍大一些,额外的空间用来记录管理信息——分配块的长度,指向下一个分配块的指针等等。这就意味着如果写超一个已分配区的尾端,则会改写后一块的管理信息。这种类型的错误是灾难性的,但是因为这种错误不会很快就暴露出来,所以也就很难发现。将指向分配块的指针向后移动也可能会改写本块的管理信息。” 以上这段话已经给了我们一些信息了。malloc()申请的空间实际我觉得就是分了两个不同性质的空间。一个就是用来记录管理信息的空间,另外一个就是可用空间了。而用来记录管理信息的实际上是一个结构体。在C语言中,用结构体来记录同一个对象的不同信息是天经地义的事!下面看看这个结构体的原型: struct mem_control_block { int is_available; //这是一个标记 int size; //这是实际空间的大小 }; free()就是根据这个结构体的信息来释放malloc()申请的空间,malloc()申请空间后返回一个指针应该是指向第二种空间,也就是可用空间。 看看free()的源代码 void free(void *ptr) { struct mem_control_block *free; free = ptr - sizeof(struct mem_control_block); free->is_available = 1; return; } 看一下函数第二句,这句非常重要和关键。其实这句就是把指向可用空间的指针倒回去,让它指向管理信息的那块空间,因为这里是在值上减去了一个结构体的大小! 那么,我之前有个错误的认识,就是认为指向那块内存的指针不管移到那块内存中的哪个位置都可以释放那块内存!但是,这是大错特错!释放是不可以释放一部分的!首先这点应该要明白。而且,从free()的源代码看,ptr只能指向可用空间的首地址,不然,减去结构体大小之后一定不是指向管理信息空间的首地址。所以,要确保指针指向可用空间的首地址!不信吗?自己可以写一个程序然后移动指向可用空间的指针,看程序会不会崩! C/C++ 四步成可执行文件 预处理: arm-linux-cpp工具 gcc –E hello.c >hello.i 把需要的代码放到一块,不做语法检查。 展开头文件,宏替换,去掉注释,条件编译 mian.i 编译: ccl工具 gcc-s hello.c 语法分析 检查语法,生成汇编 mian.s 汇编: arm-linux-as 汇编翻译成机器码,linux上一般是ELF目标文件(OBJ文件) mian.o 连接: arm-linux-ld 链接到一起生成可执行文件 将生成的obj文件和系统库的obj文件、库文件连接起来,生成可执行文件 a.out 静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。静态链接是指把要调用的函数或者过程链接到可执行文件中,成为可执行文件的一部分。 动态链接所调用的函数代码并没有被拷贝到应用程序的可执行文件中去,而是仅仅在其中加入了所调用函数的描述信息(往往是一些重定位信息)。仅当应用程序被装入内存开始运行时,在Windows的管理下,才在应用程序与相应的DLL之间建立链接关系。当要执行所调用DLL中的函数时,根据链接产生的重定位信息,Windows才转去执行DLL中相应的函数代码。 编译优化: -O1 提供基础级别的优化 -O2提供更加高级的代码优化,会占用更长的编译时间 -O3提供最高级的代码优化 宋宝华: 关于Linux编译优化几个必须掌握的姿势 - 腾讯云开发者社区-腾讯云 Linux下gcc 优化等级的介绍_yanghan1233的博客-CSDN博客 register关键字 --现在不常用了 若一个变量被register来修饰,则该变量是一个寄存器变量 作用: 请求编译器把这个修饰的变量尽可能放在cpu内部寄存器中,减少CPU到内存中抓取数据的次数,从而提高程序的执行效率。 注意: 是尽可能,不是绝对可以,因为一个cpu寄存器也就几个到几十个(如:X86:十几个ARM:最多37个)资源有限,不可能每个都可以放在寄存器中。 一般频繁被访问的变量用register来修饰; 注意点: 1、register只能修饰局部变量,不能修饰全局变量和函数;//编译器会报错,其次寄存器变量是没法取地址的,而全局变量默认是可以取地址的。即不能是全局或静态的。 2、register变量必须是能够被CPU所接受的类型,意味着register变量必须是一个单个的变量,变量长度小于等于寄存器长度; 3、因为register变量可能不放在内存中,C语言中不能对register变量使用“&”,C++编译器做了优化,可以寄存器变量放入内存中,可以取地址; 4、由于寄存器的数量有限,尽量在大量频繁操作时使用寄存器变量,且声明变量的个数应该尽量少。 宏:offsetof 求结构体中一个成员在该结构体中的偏移量。 printf("address 结构中的 name 偏移 = %d 字节。\n", offsetof(struct address, name)); strlen( )求得的是字符串的长度 sizeof( )计算字符串占的总内存空间,生成字符串时,记得加‘\0’。 printf("sizeof?size?is?%d\n",sizeof("aap"));?//size?is?4 printf("strlen?is?%d\n",strlen("aap"));?//size?is?3 字符串: 字符串一旦被创建就存在于常量池中。以字符数组形式创建的字符串,实际上是从字符串常量池中复制了一个副本,所以修改字符数组的内容时,只是修改的自己的副本,并不会影响到常量池中的字符串。而对字符串指针strp操作时会影响到常量池中的字符串。 <高质量C C++编程指南>这本书上说:?指针p 指向常量字符串(位于常量存储区),常量字符串的内容是不可以被修改的,企图修改常量字符串的内容而导致运行错误。所以这个问题出现的原因是char*p=”abcdefghi”,赋值的是字符串常量,存储在常量存储区,而常量存储区的内容是无法修改的。 数据类型: 在计算时,要注意数据类型转换过程中的数据溢出。 各个数据类型长度: double: 1.7E 就是1.7乘以10的几次方的意思 创龙 AM5708
结论:运算过程中,int乘float 结果为float,int乘int结果还是int 如果不是这样,那么bmp四字节对齐+3)*4/4 就没有意义了。
假设我的CPU是32位数据线,那么一次取数据必须是4字节,不可能只取一字节。如果我非要取一字节的话,也只能从内存里一次取4字节,然后把其中的一字节拿来使用。回到开始的32位假设,我若是要取地址为0~3中的任意一个字节,很容易实现,CPU会把取到的4个字节中拿一个给你用。具体算法是当地址部件发出逻辑地址的时候,CPU会把地址信号中的高30位当成32数据线的地址偏移,低2位会当成32位数据线的线内偏移。如果你取0~3字节的内容,高30位是0,数据线只需要传输第0个32位的数据过来给你选择就行了,如果取4~7字节的内容,高30位就是1,只需要传输第1个32的数据过来,然后4号地址对应的偏移是0,7号地址对应的偏移是3,以此类推。这是取单字节的情况。也就是说CPU传输数据的时候始终是4字节对齐的,从0开始,从4开始。假如一次取2个字节的话,假设数据线对准了高30位对准了0,那么你可以从0、1、2取两个字节,但是不能从3取,因为从3取的话还需要取第四字节的内容,这时候需要把地址线的高30位调整成0000000……0001来。如果一次取3字节的话,只能从0、1开始,对于一次取4字节的话只能从0开始了。 通俗来说就是“如果一次取一个数据块出来,必须使这个块在数据线对准的范围之内(ps四的倍数),否则只能移动数据线对准的地方,也就是多次才能取出来”。 这就是地址对齐的原因了,而且大部分CPU不能一次取3字节。对于一次取一个字节,无需对齐,因为数据线始终能在第一次瞄准的时候就把这个字节取出来。其他数据块的读取就需要考虑CPU的数据宽度了。 BMP四字节对齐后,起码我在取每行数据的时候是对齐的。 四舍五入 计算机****************************************************************************************************************** 哈佛结构是将程序中的数据和指令分开来存储,这样数据和指令可以有不同的位宽。 冯诺依曼结构是将程序中数据和指令存储在同一个存储器的不同物理地址中,这样数据和指令必须保持一致的位宽。 哈佛结构和冯·诺依曼结构的区别_顺其自然~的博客-CSDN博客_冯诺依曼结构和哈佛结构的区别 ALU:算术逻辑单元(Arithmetic&logical Unit)是中央处理器(CPU)的执行单元,是所有中央处理器的核心组成部分,由"And Gate"(与门) 和"Or Gate"(或门)构成的算术逻辑单元,主要功能是进行二位元的算术运算,如加减乘(不包括整数除法)。基本上,在所有现代CPU体系结构中,二进制都以补码的形式来表示。 对二进制整数执行算术运算或位运算的组合逻辑数字电路。 ALU 与浮点数运算单元(FPU)不同,后者仅对浮点数进行操作。ALU 是许多类型的计算电路的基本部件,这些计算电路包括计算机的中央处理单元(CPU)、浮点处理单元(FPU)和图形处理单元(GPU)。单个CPU、FPU 或 GPU 可能包含多个 ALU。 ALU 的输入包括需要运算的数据(也称为运算数)和表明了运算操作类型的指令码。ALU 的输出是其执行运算的结果。在许多的设计中,ALU 还带有状态输入或输出,可将其之前操作或当前操作的信息在 ALU 和外部状态寄存器间传递。 DSP 大量数字运算,循环运算。 如连续1024个乘加。他的指令针对这种应用有特殊的处理,相比RISC可以更快速高效地完成这类运算。 MCU、DSP、FPGA这些都属于嵌入式系统的范畴,是为了实现某一目的而使用的工具。 DSP与单片机的区别: 1、存储器结构不同 单片机使用冯.诺依曼存储器结构。这种结构中,只有一个存储器空间通过一组总线(一个地址总线和一个数据总线)连接到处理器核。 大多数DSP采用了哈佛结构,将存储器空间划分成两个,分别存储程序和数据。 2、 对密集的乘法运算的支持 单片机不是设计来做密集乘法任务的,即使是一些现代的GPP,也要求多个指令周期来做一次乘法。而DSP处理器使用专门的硬件来实现单周期乘法。DSP处理器还增加了累加器寄存器来处理多个乘积的和。累加器寄存器通常比其他寄存器宽,增加称为结果bits的额外bits来避免溢出。 3、 零开销循环 DSP算法的一个共同的特点,即大多数处理时间都花在执行较小的循环上,也就容易理解,为什么大多数的DSP都有专门的硬件,用于零开销循环。所谓的零开销循环是指处理器在执行循环时,不用花时间去检查循环计数器的值,条件转移到循环大额顶部,将循环计数器减1。 信息的表示和处理: 在计算机中有符号数,有原码、反码、补码三种表示方式。 在计算机中默认使用补码的形式表示。 补码,人类直观是看不出来多少的。 数字的产生只是为了计数吗?计数和处理。 数字是古代印度人在生产和实践中逐步创造出来的。 在古代印度,进行城市建设时需要设计和规划,进行祭祀时需要计算日月星辰的运行,于是,数学计算就产生了。大约在公元前3000多年,印度河流域居民的数字就比较先进,而且采用了十进位的计算方法。 “数”是量度事物的概念。是客观存在的量的意识表述。“数字”起源于原始人类用来数数计数的记号形成自然数“数”的符号,是人类最伟大的发明之一,是人类精确描述事物的基础。在人类漫长的历史进程中, 1° 通过对现实事物数数这种方式得到了数; 2° 数可以使用一定的方式进行运算; 3° 数同空间事物相联系时,可表明这些事物的多少。(摘自自然数原本数数论) 反码,补码: 正数的补码反码是其本身。 负数的反码是符号位保持不变,其余位取反;补码是在其反码的基础上+1。 计算机原码,反码,补码_chenchao2017的博客-CSDN博客_补码 原码表示正负数: 上图中左边每增加一个二进制单位对应的真数是递减的,而右边每增加一个二进制单位对应的真数是递增的,所以对于原码来说,能满足正数的加法,但无法满足负数的加法。 1+-1=[0000_00001]原+[1000_0001]原=[1000_0010]原=-2 为了满足负数对加法的需求,就必须让负数与他对应的二进制码是同步递增或者同步递减。于是就通过符号位不变,其余位取反来满足这个同步递增或者递减的要求,由于正数本来就满足它本身的加法,所以不需要做任何改变。这就是反码的定义由来。 反码: 从上图的反码表中可以看到在运算不跨过0的时候,正负数的加法已经能满足要求 -2+1=[1111_1101]反+[0000_0001]反=[1111_1110]反=-1 [1111_1111]和[0000_0000]都表示0,这导致在实际计算中每当跨过0一次,就有一个单位的误差 -1+2=[1111_1110]反+[0000_0010]反=[0000_0000]反=0 让反码中的[1111_1111]和[0000_0000]合并 由于[1111_1111]+[0000_0001]=[0000_0000],所以在负数反码的基础上+1就可以解决反码中跨0的误差问题,同时不会对负数与它对应的二进制反码的同步递增产生影响,所以在反码的基础上+1就完美的解决了符号参与预算的问题,这就是补码为什么是在负数反码的基础上+1的由来。 补码: 从上面的图中发现还有一个[1000_0000]的二进制没有对应任何真数,于是就规定了这个数的真数是-128。 所以补码的表示范围是[-128~127] ,这样一来256个二进制正好表示256个整数,在实际二进制的运算中超过范围其实就是对256的取余预算(x+128)mod 256 - 128。 很多太底层的数学抽象推理,不太好理解,但是在实践中得注意到。 |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/23 10:04:17- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |