-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 381 KB
/
content.json
1
{"meta":{"title":"Tom's develop Blog","subtitle":"Love technology,love life.","description":"keep curiously!","author":"Tom","url":"https://TOMsworkspace.github.io","root":"/"},"pages":[{"title":"Link","date":"2019-11-08T08:26:24.000Z","updated":"2024-10-11T18:29:55.335Z","comments":true,"path":"link/index.html","permalink":"https://tomsworkspace.github.io/link/index.html","excerpt":"","text":""},{"title":"Category","date":"2019-11-08T08:22:30.000Z","updated":"2024-10-11T18:29:55.335Z","comments":false,"path":"categories/index.html","permalink":"https://tomsworkspace.github.io/categories/index.html","excerpt":"","text":""},{"title":"Tags","date":"2019-11-08T08:19:57.000Z","updated":"2024-10-11T18:29:55.335Z","comments":false,"path":"tags/index.html","permalink":"https://tomsworkspace.github.io/tags/index.html","excerpt":"","text":""},{"title":"about","date":"2024-10-21T16:46:27.000Z","updated":"2024-10-21T17:19:29.771Z","comments":false,"path":"about/index.html","permalink":"https://tomsworkspace.github.io/about/index.html","excerpt":"","text":"About me 熟练掌握 C/C++语言,STL、继承、多态、面向对象、模板、多线程。 熟练使用 git/github,熟悉分支工作流程,熟悉开源项目开发流程,有参与开源项目经验。 熟练使用 GCC/MSVC/clang/Android ndk,make,cmake等编译构建工具链。 熟悉并参与过软件ci/cd流程建设和实践,如静态扫描、地址检查、单元测试等。 熟练使用MAC/Linux/Windows开发环境及工具链。 有软件性能优化相关经历,熟悉SIMD指令(arm neon/X86 avx、sse等),熟悉常见并行编程框架(PPL, pthread, Apple GCD等)。 有AI算法部署及推理优化相关经历,如部署推理框架(MNN,PyTorch);端上推理硬件加速方案如:gpu加速(OpenCL/CUDA/CoreML)、npu加速(SNPE/hiai/mtk/CoreML)等,软件加速方案如:OpenVINO/TenserRT等。"}],"posts":[{"title":"流体模拟与SPH方法","slug":"流体模拟与SPH方法","date":"2021-09-27T08:46:17.000Z","updated":"2024-10-11T18:29:55.335Z","comments":true,"path":"2021/09/27/流体模拟与SPH方法/","link":"","permalink":"https://tomsworkspace.github.io/2021/09/27/%E6%B5%81%E4%BD%93%E6%A8%A1%E6%8B%9F%E4%B8%8ESPH%E6%96%B9%E6%B3%95/","excerpt":"","text":"流体的物理模拟与SPH方法 在引入有限元之前,先简单介绍相关的物理理论。在后面的部分,使用粗体符号如 $\\boldsymbol{x}$ 表示向量或矩阵(张量), 未加粗的符号为标量, 如 $x$。 1、拉格朗日视角与欧拉视角  烟雾、海浪、水滴…,这些司空见怪的自然现象其实有着非常复杂的数学规律,对于流体等连续介质材料的研究,有两种完全不同的视角,分别是欧拉视角和拉格朗日视角。  欧拉视角的坐标系是固定的,如同站在河边观察河水的流动一样,用这种视角分析流体需要建立网格单元,使用网格将流体分成一个个小的“单元”,在这些单元上进行流体的物理模拟,计算相关的物理量等,这种思路与有限元方法联系了起来。而拉格朗日视角则将流体视为流动的单元,例如将一片羽毛放入风中,那么羽毛的轨迹可以帮我们指示空气的流动规律。 SPH算法是典型的拉格朗日视角,它的基本原理就是通过粒子模拟来流体的运动规律,然后再通过表面重建算法(Marching Cube)从粒子中生成网格来进行流体渲染。 2、SPH粒子受力分析 SPH方法将流体看作大量粒子的集合,使用粒子的运动来模拟流体的流动,根据某点周围粒子的数量来计算流体在该点的压强、流速、密度等属性,同时又使用这些信息来更新粒子的运动情况。  SPH算法的基本设想,就是将连续的流体想象成一个个相互作用的微粒,这些例子相互影响,共同形成了复杂的流体运动,对于每个单独的流体微粒,依旧遵循最基本的牛顿第二定律。流体的质量是由流体单元的密度决定的,所以在SPH中一般用密度代替质量来表示力: $$ \\boldsymbol{F} = \\rho \\boldsymbol{a} \\tag{1}$$  作用在一个粒子上的作用力由三部分组成: $$\\boldsymbol{F} = \\boldsymbol{F}^{external} + \\boldsymbol{F}^{pressure} + \\boldsymbol{F}^{viscosity} \\tag{2}$$   其中,$\\boldsymbol{F}^{external}$ 是外力,一般就是重力,所以: $$\\boldsymbol{F}^{external} = \\rho \\boldsymbol{g} \\tag{3}$$   $\\boldsymbol{F}^{pressure}$ 是由流体内部的压力差产生的作用力,数值上等于压力场在这一点的梯度,力的方向有压力高的地方指向压力低的地方,所以: $$\\boldsymbol{F}^{pressure} = - \\boldsymbol{\\nabla p} \\tag{4}$$   $\\boldsymbol{F}^{viscosity}$ 是由于粒子间的速度差引起的力,设想在流动的液体内部,快速流动的部分会施加类似于剪切力的作用力到速度慢的部分,这个力的大小跟流体的粘度系数 $\\mu$ 以及速度差有关,所以: $$\\boldsymbol{F}^{viscosity} = \\mu\\boldsymbol{\\nabla^{2} u} \\tag{5}$$ 这里的 $\\boldsymbol{u}$ 是此处的速度, $\\boldsymbol{\\nabla^{2}}$ 是拉普拉辛算子,也叫二阶微分算子,有时也可写作 $\\boldsymbol{\\Delta}$或者$\\boldsymbol{\\nabla\\cdot\\nabla}$。  由(1)-(5),可以得到流体任一点粒子的加速度为: $$\\boldsymbol{a} = \\boldsymbol{a^{external}} + \\boldsymbol{a^{pressure}} + \\boldsymbol{a^{viscosity}} = \\boldsymbol{g} - \\frac{\\boldsymbol{\\nabla p}}{\\rho} + \\frac{\\mu\\boldsymbol{\\nabla^{2} u}}{\\rho} \\tag{6}$$  根据这个公式,只要知道了某一点粒子的流体密度以及压强,就可以使用时间积分方法更新粒子的位置和速度了。下面来说流体某一点密度及压强的计算。 3、光滑核函数 SPH方法将流体看作大量粒子的集合,使用粒子的运动来模拟流体的流动,根据某点周围粒子的数量来计算流体在该点的压强、流速、密度等属性,同时又使用这些信息来更新粒子的运动情况。可以这样理解这个概念,粒子的属性都会“扩散”到周围,我们使用一个函数来加权求和某一点的属性,这个函数要使得随着距离的增加对结果的影响逐渐变小,也就是在累加中的权重变小,这种随着距离而衰减的函数被称为“光滑核”函数,最大影响半径 $h$ 为“光滑核半径”。  常用的核函数,比如Cubic soline kernel,高斯核等。  设想流体中某点 $\\boldsymbol{x}$(此处不一定有粒子),在光滑核半径h范围内有数个粒子,位置分别是$\\boldsymbol{x_0}$,$\\boldsymbol{x_1}$,$\\boldsymbol{x_2}$,…,$\\boldsymbol{x_j}$,则该处某项属性 A (可以是压强 $p$、流速 $u$、密度 $rho$ 等属性)的累加公式为: $$\\boldsymbol{A}(x) = \\sum_j \\boldsymbol{A}_j \\frac{m_j}{\\rho_j}W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h) \\tag{7}$$  根据(7),某个属性在任意一点的梯度为: $$\\boldsymbol{\\nabla A}(x) = \\sum_j \\boldsymbol{A}_j \\frac{m_j}{\\rho_j} \\boldsymbol{\\nabla_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h) \\tag{8}$$   某个属性在任意一点的二阶微分为: $$\\boldsymbol{\\nabla^{2} A}(x) = \\sum_j \\boldsymbol{A}_j \\frac{m_j}{\\rho_j} \\boldsymbol{\\nabla^{2}_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h) \\tag{9}$$  这里的 $\\boldsymbol{\\nabla^{2}_x} W$ 表示核函数在 $\\boldsymbol{x}$ 处的二阶微分。 3、密度 对于任一点的流体,其密度为: $$\\rho(x) = \\sum_j \\rho_j \\frac{m_j}{\\rho_j}W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h) = \\sum_j m_j W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h) \\tag{10}$$ 4、压力 对于任意某个粒子,产生的压强可以这样计算: $$p_j = B ((\\frac{\\rho_j}{\\rho_{j0}})^{\\gamma} - 1) \\tag{11}$$  这里的 $B$ 是体积模量(Bulk module), $\\rho$ 是粒子所在点的流体密度,$\\gamma (\\sim 7)$ 是一个常数。  根据(8), (6)中的压强产生的加速度部分可表示为: $$\\boldsymbol{a^{pressure}(x)} = - \\frac{\\boldsymbol{\\nabla p(x)}}{\\rho(x)} = - \\frac{\\boldsymbol{\\sum_{j} p_j \\frac{m_j}{\\rho_j} \\boldsymbol{\\nabla_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h)}}{\\rho(x)} \\tag{12}$$  不过不幸的是,这个公式是“不平衡”的,也就是说,位于不同压强区的两个粒子之间的作用力不等,所以计算中一般使用双方粒子压强的算术平均值代替,任意处由压力产生的作用力的计算公式为: $$\\boldsymbol{a^{pressure}(x)} = - \\frac{\\boldsymbol{\\sum_{j} \\frac{m_j(p(x) + p_j)}{2 \\rho_j} \\boldsymbol{\\nabla_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h)}}{\\rho(x)} \\tag{13}$$ 4、粘度 根据(8), (7)中由粘度产生的加速度部分可表示为: $$\\boldsymbol{a^{viscosity}(x)} = \\frac{\\mu\\boldsymbol{\\nabla^{2} u(x)}}{\\rho(x)} = \\frac{\\boldsymbol{\\sum_{j} \\boldsymbol{u_j} \\frac{m_j}{\\rho_j} \\boldsymbol{\\nabla^{2}_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h)}}{\\rho(x)} \\tag{14}$$  这个公式同样是“不平衡”的,由于速度是粒子间的相对速度,所以把它修正为: $$\\boldsymbol{a^{viscosity}(x)} = \\frac{\\boldsymbol{\\sum_{j} \\boldsymbol{u_j} m_j\\frac{u_j - u(x)}{\\rho_j} \\boldsymbol{\\nabla^{2}_x} W(||\\boldsymbol{x} - \\boldsymbol{x_j}||_2, h)}}{\\rho(x)} \\tag{15}$$ 5、SPH方法 综上所述,在每次迭代中,SPH的计算步骤为: (1)根据公式(10)计算每个粒子处的密度。(2)根据公式(11)计算每个粒子处的压强。(3)根据公式(8)(13),计算所在位置的压强梯度及压强差产生的力的加速度。(4)根据公式(9)(15),计算所在位置的速度二阶微分及速度差产生的力的加速度。(5)根据公式(6),计算每个粒子的加速度。(6)使用时间积分方法更新粒子速度和位置。如显式欧拉法: $$\\boldsymbol v_{t+1} = \\boldsymbol v_{t} + ∆t \\boldsymbol a$$ $$\\boldsymbol x_{t+1} = \\boldsymbol x_{t} + ∆t \\boldsymbol v_{t+1}$$ (7)根据更新后的粒子重建流体表面,渲染出流体。  具体地,SPH方法有许多变种,比如WCSPH, PCISPH, DFSPH等。都是基于以上的思路,在具体的公式上有一些细微的区别。具体可参考相应的论文。 参考资料https://interactivecomputergraphics.github.io/SPH-Tutorial/ https://blog.csdn.net/liuyunduo/article/details/84098884 https://blog.csdn.net/qq_39300235/article/details/100982901 WCSPH: M. Becker and M. Teschner (2007). “Weakly compressible SPH for free surface flows”. In:Proceedings of the 2007 ACM SIGGRAPH/Eurographics symposium on Computer animation. Eurographics Association, pp. 209–217. PCISPH: B. Solenthaler and R. Pajarola (2009). “Predictive-corrective incompressible SPH”. In: ACM SIGGRAPH 2009 papers, pp. 1–6. DFSPH: J. Bender, D. Koschier (2015) Divergence-free smoothed particle hydrodynamics[C] //Proceedings of the 14th ACM SIGGRAPH/Eurographics symposium on computer animation. ACM, 2015: 147-155.","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"Computer Graphics","slug":"Computer-Graphics","permalink":"https://tomsworkspace.github.io/categories/Computer-Graphics/"},{"name":"Physics engine","slug":"Physics-engine","permalink":"https://tomsworkspace.github.io/categories/Physics-engine/"}],"tags":[{"name":"流体模拟","slug":"流体模拟","permalink":"https://tomsworkspace.github.io/tags/%E6%B5%81%E4%BD%93%E6%A8%A1%E6%8B%9F/"},{"name":"SPH方法","slug":"SPH方法","permalink":"https://tomsworkspace.github.io/tags/SPH%E6%96%B9%E6%B3%95/"},{"name":"拉格朗日视角与欧拉视角","slug":"拉格朗日视角与欧拉视角","permalink":"https://tomsworkspace.github.io/tags/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E8%A7%86%E8%A7%92%E4%B8%8E%E6%AC%A7%E6%8B%89%E8%A7%86%E8%A7%92/"}]},{"title":"弹性有限元方法","slug":"弹性有限元方法","date":"2021-09-11T09:05:34.000Z","updated":"2024-10-11T18:29:55.334Z","comments":true,"path":"2021/09/11/弹性有限元方法/","link":"","permalink":"https://tomsworkspace.github.io/2021/09/11/%E5%BC%B9%E6%80%A7%E6%9C%89%E9%99%90%E5%85%83%E6%96%B9%E6%B3%95/","excerpt":"","text":"弹性有限元与超弹性物体模拟方法 在引入有限元之前,先简单介绍相关的物理理论。在后面的部分,使用粗体符号如 $\\boldsymbol{x}$ 表示向量或矩阵(张量), 未加粗的符号为标量, 如 $x$。 1、形变(Deformation)当弹性材料发生形变,其上点$x$会移动到新的位置,我们形变映射(Deformation map) $\\boldsymbol \\phi$ 表达此关系,它是一个向量到向量的映射。有: $$\\boldsymbol x_{deformed} = \\boldsymbol \\phi( \\boldsymbol x_{rest})$$ 为了更好地描述形变,通常使用形变梯度(deformation gradient)来表示这个形变。形变梯度定义为: $$\\boldsymbol F = \\frac{\\partial \\boldsymbol \\phi( \\boldsymbol x_{rest})}{\\partial \\boldsymbol x_{rest}} = \\frac{\\boldsymbol x_{deformed}}{\\boldsymbol x_{rest}} \\tag{1}$$ 这里 $\\boldsymbol F$是一个n阶张量,2D问题就是一个二阶张量(2 x 2 矩阵),3D就是一个三阶张量(3 x 3矩阵)。 $\\boldsymbol F$的行列式(通常用$\\boldsymbol J$表示)也是非常有用的,因为它的表征无限小体积的变化。它通常表示为: $$\\boldsymbol J = det(\\boldsymbol F) \\tag{2}$$ $\\boldsymbol J$表示在无限小体积(面积)相对原始体积(面积)的比率。例如,在刚性运动(旋转和平移)中这很好理解, $\\boldsymbol F$是旋转矩阵且$\\boldsymbol J = 1$。请注意,单位矩阵也是旋转矩阵。 $\\boldsymbol J > 1$表示体积(面积)增加,$\\boldsymbol J < 1$表示体积(面积)减小。 $\\boldsymbol J = 0$意味着体积变成0。在真实世界中,这将不会发生。然而,数值上获得这样的$\\boldsymbol F$是可能的。在3D中,这表明物质被压缩使其成为一个面或一条线或是一个无体积的点。 $\\boldsymbol J < 0$意味着物体被反转。考虑2D中的一个三角形, $\\boldsymbol J < 0$意味着某个顶点穿过了它相对的边,然后面积变成了负值。 2、弹性(Elasticity)2.1 应变势能(Strain energy)与应力(stress)  对于超弹性材料,其应力与应变关系由一个应变能量密度函数定义: $$\\Psi = \\Psi(\\boldsymbol{F}) \\tag{3}$$ 直观地理解:$\\Psi $ 是惩罚形变的隐函数。这个势函数与物体抵抗形变所产生的力有关,这个力叫应力(stress),它是材料内部的一种弹性力。而之前 (1) 定义的形变梯度$\\boldsymbol F$也叫做应变(strain)。 知道了势能密度之后,对于一个物体,其总的弹性势能就是: $$E = \\int_{\\Omega} \\boldsymbol{\\Psi(F)} d \\boldsymbol{X} \\tag{4}$$ 因此,物体在任意一点的受力可以通过势能的负梯度来计算: $$ \\boldsymbol {f(x)} = - \\frac{\\partial E}{\\partial \\boldsymbol{x}} \\tag{5}$$ 应力代表无限小体积(面积)的材料成分对其附近施加的内力。为了表示应力,定义了不同的度量来表示: First Piola-Kirchhof(PK1)应力 $\\boldsymbol {P(F)} = \\frac{\\partial \\boldsymbol{\\Psi}( \\boldsymbol{F})}{\\partial \\boldsymbol{F}} $ Kirchhoff 应力: $\\boldsymbol \\tau$ 柯西(cauchy)应力: $\\boldsymbol \\sigma$ 三种应力都是一个张量,维度与应变 $\\boldsymbol F$ 一样。三种应变可以相互转化:$$\\boldsymbol \\tau = J\\boldsymbol \\sigma = \\boldsymbol{PF}^{T}, \\boldsymbol P = J \\boldsymbol {\\sigma F}^{-T} \\tag{6}$$   图形学模拟里一般常用PK1应力与柯西应力。 2.2 弹性模量(Elastic moduli)  物理模拟中,常常使用各向同性模型,指的沿物体任意一个方向施加形变,沿该方向对抗该形变的力都是一样的。为了描述各向同性物体的弹性,引入弹性模量来度量弹性,有以下几种: Young’s modulus $E = \\frac{\\sigma}{\\varepsilon}$ Bulk modulus $K = - V \\frac{dP}{dV}$ Poisson’s ratio $\\nu \\in [0 , 0.5]$ Lam$\\acute{e}$’s first parameter $\\mu$ Lam$\\acute{e}$’s second parameter $\\lambda$ 他们之间也可以相互转化, $$K = \\frac{E}{3(1 - 2\\nu)}, \\lambda = \\frac{E\\nu}{(1 + \\nu)(1 - 2\\nu)}, \\mu = \\frac{E}{2(1 + \\nu)} \\tag{7}$$   图形学模拟里一般常常设定 $E$ 与 $\\nu$ 的值, 其他值通过这两个算出来。 2.3 超弹性材料模型  图形学里常常使用一个确定的函数来描述弹性势能与应变间的关系,进而将应变与应力联系起来。一般常用的线性弹性模型有: (1)Neo-Hookean模型 $$\\boldsymbol{\\Psi}( \\boldsymbol{F}) = \\frac{\\mu}{2}\\sum_{i}[(\\boldsymbol{FF^{T}})_{ii}-1]-\\mu log(J) + \\frac{\\lambda}{2}log^{2}(J) \\tag{8}$$ $$\\boldsymbol{P}( \\boldsymbol{F}) = \\frac{\\partial \\Psi}{\\partial \\boldsymbol F} = \\mu(\\boldsymbol {F-F^{T}}) + \\lambda log(J) \\boldsymbol{F^{-T}} \\tag{9}$$ (2) (Fixed) Corotated模型 $$\\boldsymbol{\\Psi}( \\boldsymbol{F}) = \\mu \\sum_{i}(\\boldsymbol{\\sigma_{i}-1})^{2}- \\frac{\\lambda}{2}(J - 1)^{2} \\tag{10}$$ $$\\boldsymbol{P}( \\boldsymbol{F}) = \\frac{\\partial \\Psi}{\\partial \\boldsymbol F} = 2\\mu(\\boldsymbol {F-R}) + \\lambda (J - 1)J\\boldsymbol{F^{-T}} \\tag{11}$$  公式(10)里的 $\\sigma_{i}$ 是应变 $\\boldsymbol F$的奇异值。公式(11)里的 $R$ 是 $F$ 进行极分解 $\\boldsymbol {F = RS}$ 后得到的。 3、线性有限元方法(Linear finite element method)  有限元方法是一种 Galerkin 离散化方法,使用连续偏微分方程的弱形式来构建离散方程。直观地讲,就是讲一个连续的物体或表面划分为一个个微元,也就是element,在一个个微元上根据物理性质来构建方程逼近连续的求解结果。  在物理模拟中,常常使用三角形(2D)或四面体(3D)来作为相应的有限元element。  引入有限元之后,公式(4)可以重写成: $$E = \\sum_{e}E^{e}(x) = \\sum_{e} \\int_{\\Omega_{e}} \\boldsymbol{\\Psi(F)} d \\boldsymbol{X} = \\sum_{e} V_{e} \\boldsymbol{\\Psi(F_{e})} \\tag{12}$$  相应地,每个微元上的点受力可以写成: $$ \\boldsymbol {f(x)} = - \\frac{\\partial E(x)}{\\partial \\boldsymbol{x}}= - \\sum_{e} \\frac{\\partial E^{e}(x)}{\\partial \\boldsymbol{x}}= - \\sum_{e} V_{e}\\frac{\\partial \\boldsymbol\\Psi(\\boldsymbol F_{e})}{\\partial {\\boldsymbol F_{e}}} \\frac{\\partial \\boldsymbol F_{e}}{\\partial {\\boldsymbol x}}= - \\sum_{e} V_{e} \\boldsymbol P(\\boldsymbol F_{e}) \\frac{\\partial \\boldsymbol F_{e}}{\\partial {\\boldsymbol x}} \\tag{13}$$ 3.1 四面体有限元与三角形有限元 线性有限元(用于弹性)假设形变映射是仿射的,因此变形梯度 $\\boldsymbol F$ 在单个element内是常量。  对于四面体微元,有 表示成 $$\\boldsymbol D_{S}= \\boldsymbol F \\boldsymbol D_{m} \\tag{14}$$ 其中 $\\boldsymbol D_{s} = \\left[\\begin{matrix}\\boldsymbol x_{1} - \\boldsymbol x_{4} & \\boldsymbol x_{2} - \\boldsymbol x_{4} & \\boldsymbol x_{3} - \\boldsymbol x_{4}\\end{matrix}\\right]$ 被称为deformed shape矩阵, $\\boldsymbol D_{m} = \\left[\\begin{matrix}\\boldsymbol X_{1} - \\boldsymbol X_{4} & \\boldsymbol X_{2} - \\boldsymbol X_{4} & \\boldsymbol X_{3} - \\boldsymbol X_{4}\\end{matrix}\\right]$ 被称为reference shape矩阵。它是一个常数矩阵,只与初始状态有关。 四面体的体积可以计算为: $$V_{e} = \\frac{1}{6}|det(\\boldsymbol D_{m})| \\tag{15}$$ 由$\\boldsymbol D_{m}$ 可逆, 可以求出形变梯度 $\\boldsymbol F$ : $$\\boldsymbol F = \\boldsymbol D_{s} \\boldsymbol D_{m}^{-1} \\tag{16}$$ 为了计算四面体每个顶点的受力,需要计算 $\\frac{\\partial \\boldsymbol F_{e}}{\\partial {\\boldsymbol x}}$, 这里直接给出受力的计算结果。具体推导看这里。 $$\\boldsymbol H = [\\boldsymbol f_{i}, \\boldsymbol f_{2}, \\boldsymbol f_{3}] = -V_{e} \\boldsymbol P(\\boldsymbol F) \\boldsymbol D_{m}^{-T} \\tag{17}$$ 这里的 $\\boldsymbol P(\\boldsymbol F)$ 可以直接使用公式(9)或(11)的结果。$\\boldsymbol H$分别是1,2,3三个顶点的受力,另一个顶点4的受力为 $\\boldsymbol f_{4} = - \\boldsymbol f_{1} - \\boldsymbol f_{2} - \\boldsymbol f_{3}$ 。  基于之上的讨论,一个可以用于计算四面体有限元弹性力的伪代码流程如下:  对于三角形网格,其推导是类似的,只是少了一个维度。 3.2 显式时间积分 基于上一节的讨论,已经得到了计算每个每个顶点上的受力。使用半隐式欧拉迭代方法更新点的位置和速度。 $$\\boldsymbol v_{t+1,i} = \\boldsymbol v_{t,i} + \\Delta t \\frac{\\boldsymbol f_{t,i} }{m_{i}} \\tag{18}$$ $$\\boldsymbol x_{t+1,i} = \\boldsymbol x_{t,i} + \\Delta t \\boldsymbol v_{t+1,i}\\tag{19}$$ 3.3 隐式时间积分 隐式时间积分的更新迭代如下: $$[\\boldsymbol I - \\Delta t^{2}\\boldsymbol M ^{-1}\\frac{\\partial \\boldsymbol f}{\\partial \\boldsymbol x}(\\boldsymbol x_{t})]\\boldsymbol v_{t+1} = \\boldsymbol v_{t} + \\Delta t \\boldsymbol M^{-1}\\boldsymbol f(\\boldsymbol x_{t}) \\tag{20}$$ 这还需要计算力的导数 $\\frac{\\partial \\boldsymbol f}{\\partial \\boldsymbol x}$, $-\\frac{\\partial \\boldsymbol f}{\\partial \\boldsymbol x}$被称为stiffness matrix,在实际迭代求解中我们并不用真的构建这个矩阵,我们只需要知道它和某个向量 $ \\boldsymbol w$ 的乘积结果即可进行迭代。下面的算法是力的微分$\\delta \\boldsymbol f = \\frac{\\partial \\boldsymbol f}{\\partial \\boldsymbol x} \\delta \\boldsymbol x$的计算方式: 具体推导。 4、引用[1] E. Sifakis and J. Barbic (2012). “FEM simulation of 3D deformable solids: a practitioner’s guide to theory, discretization and model reduction”. In: Acm siggraph 2012 courses, pp. 1–50. [2] C. Jiang et al. (2016). “The material point method for simulating continuum materials”. In: ACM SIGGRAPH 2016 Courses, pp. 1–52.","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"Computer Graphics","slug":"Computer-Graphics","permalink":"https://tomsworkspace.github.io/categories/Computer-Graphics/"},{"name":"Physics engine","slug":"Physics-engine","permalink":"https://tomsworkspace.github.io/categories/Physics-engine/"}],"tags":[{"name":"超弹性物体","slug":"超弹性物体","permalink":"https://tomsworkspace.github.io/tags/%E8%B6%85%E5%BC%B9%E6%80%A7%E7%89%A9%E4%BD%93/"},{"name":"有限元方法","slug":"有限元方法","permalink":"https://tomsworkspace.github.io/tags/%E6%9C%89%E9%99%90%E5%85%83%E6%96%B9%E6%B3%95/"},{"name":"FEM","slug":"FEM","permalink":"https://tomsworkspace.github.io/tags/FEM/"}]},{"title":"弹簧质点系统与时间积分","slug":"弹簧质点系统与时间积分","date":"2021-08-09T13:39:12.000Z","updated":"2024-10-11T18:29:55.334Z","comments":true,"path":"2021/08/09/弹簧质点系统与时间积分/","link":"","permalink":"https://tomsworkspace.github.io/2021/08/09/%E5%BC%B9%E7%B0%A7%E8%B4%A8%E7%82%B9%E7%B3%BB%E7%BB%9F%E4%B8%8E%E6%97%B6%E9%97%B4%E7%A7%AF%E5%88%86/","excerpt":"","text":"弹簧质点系统一个模拟变形物体最简单的方法就是将其表示为弹簧质点系统(Mass Spring Systems)。一个弹簧质点包含了一系列由多个弹簧连接起来的质点,这样的系统的物理属性非常直接,模拟程序也很容易编写。 虽然模型简单,但是也带来了一些问题: 1.物体的行为依赖于弹簧系统的设置方法; 2.很难通过调整弹簧系数来得到想要的结果; 3.弹簧质点网格不能直接获取体效果。 在很多的应用中这些缺点可以忽略,在这种场合下,弹簧质点网格是最好的选择,因为够快够简单。弹簧质点系统可用于模拟绳索、布料、头发等弹性物体。 力是改变物体运动状态的原因,在这个系统中,主要有两种力,一是弹簧的弹力和阻尼力。 对于连接两个质点的一个弹簧,弹力是: 阻尼力可以这样算: 假设系统中有N个质点,质量为$m_i$,位置为$x_i$,速度为$v_i$ , $1 < i < N$. 这些质点由一组弹簧S连接,弹簧参数为($i$, $j$, $l_0$, $k_s$, $k_d$)。$i$,$j$为连接的弹簧质点,$l_0$为弹簧完全放松时的长度,$k_s$为弹簧弹性系数,$k_d$为阻尼系数,由胡科定律知弹簧施加在两顶点上的力可以表示为: $$ \\overrightarrow{f}_i = \\overrightarrow{f}^{s}(x_i,x_j)=k_s \\frac{x_j - x_i}{|x_j - x _i|}(|x_j-x_i|-l_0) $$ $$ \\overrightarrow{f}_j = \\overrightarrow{f}^{s}(x_j,x_i)= -\\overrightarrow{f}^{s}(x_i,x_j) = - \\overrightarrow{f_i} $$ 由受力守恒知$f_i+f_j = 0$. $f_i$和$f_j$的大小和弹簧伸长成正比关系。 对于阻尼的计算,除了与位移有关,还与质点速度有关:$$ \\overrightarrow{f_i} = \\overrightarrow{f}^{d}(x_i,v_i,x_j,v_j)=k_d(v_j - v_i)\\frac{x_j - x_i}{|x_j - x _i|} $$ $$ \\overrightarrow{f_j} = \\overrightarrow{f}^{s}(x_j,v_j,x_i,v_i) = -\\overrightarrow{f_i} $$ 大小和速度成正比,并且符合力守恒,则对于一个质点,其受合力方程为: $$ \\overrightarrow{f}(x_i,v_i)=\\sum \\overrightarrow{f}^{s}(x_i,x_j) + \\sum \\overrightarrow{f}^{d}(x_i,v_i,x_j,v_j) $$ 这里 $j$ 为所有与质点 $i$ 存在弹簧连接的质点。后面讨论的运算都是矢量运算,为了方便就省略不写了。 在计算机模拟中,牛顿第二定律 $f = ma$ 是关键。在已知质量和外力的情况下,通过 $a=f/m $可以得到加速度,将二阶常微分方程写成两个一阶方程:$$\\frac{\\mathrm{d}v}{dt} = \\frac{f(x,v)}{m} $$ $$\\frac{\\mathrm{d}x}{dt} = v$$可以得到解析解: $$v(t) = v_0 + \\int_{t_0}^{t}\\frac{f(x,v)}{m} \\mathrm{d}t$$ $$x(t) = x_0 + \\int_{t_0}^{t}v(t) \\mathrm{d}t$$ 初始状态为 $v(t_0)$ = $v_0$, $x(t_0)=x_0$。积分将时间t内所有的变化加和,模拟的过程就是从$t_0$开始不断地计算$x(t)$和$v(t)$,然后更新质点的位置。 整个过程的伪代码如下: // initialization forall particles i initialize xi , vi and mi endfor// simulation loop loop forall particles i fi ← fg + fcoll + ∑ f(xi , vi , x j , v j ) endfor forall particles i vi ← vi + ∆t fi /mi xi ← xi + ∆t vi endfor display the every nth time endloop 时间积分时间积分算法将这个积分的过程离散化,使用有限的步长去迭代下一时刻的状态。 欧拉法前向欧拉方法 (显式时间积分)$$v_{t + 1} = v_{t} + \\Delta{t}\\frac{f(x_t,v_t)}{m}$$ $$x_{t + 1} = x_{t} + \\Delta{t}v_{t}$$ 这个就是显式的欧拉解法,下一时刻的状态完全由当前状态决定。 半隐式欧拉方法(显式时间积分)$$v_{t + 1} = v_{t} + \\Delta{t}\\frac{f(x_t,v_t)}{m}$$ $$x_{t + 1} = x_{t} + \\Delta{t}v_{t + 1}$$ 它和前向欧拉的差别很小。 后向欧拉方法(隐式时间积分)$$x_{t + 1} = x_{t} + \\Delta{t}v_{t + 1}$$ $$v_{t + 1} = v_{t} + \\Delta{t}M^{-1}f(x_{t + 1},v_{t + 1})$$ 这里 $M$ 为一个 $3n * 3n$ 的对角矩阵,矩阵对角线上依次为各个质点的质量, $diag(M)=(m_1,m_1,m_1,m_2,m_2,m_2,…,m_n,m_n,m_n)$。 这是一个非线性的方程,先对 $f(x_{t + 1},v_{t + 1})$ 进行一阶泰勒展开,得 $$f(x_{t + 1},v_{t + 1}) = f(x_{t} + \\Delta{x},v_{t} + \\Delta{v})=f(x_{t},v_{t}) + \\frac{\\partial f}{\\partial x}(x_t,v_t) \\Delta{x} + \\frac{\\partial f}{\\partial v}(x_t,v_t)\\Delta{v} $$ 由$\\Delta{x} = x_{t+1}-x_{t} = \\Delta{t}v_{t+1}$, $\\Delta{v} = v_{t+1}-v_{t}$。得$$f(x_{t + 1},v_{t + 1}) = f(x_{t},v_{t}) + \\frac{\\partial f}{\\partial x}(x_t,v_t) \\Delta{t} v_{t + 1} + \\frac{\\partial f}{\\partial v}(x_t,v_t)(v_{t+1}-v_{t})$$ 用 $I$ 表示单位矩阵,代入上面的式子,对上式进行移项,有 $$(I - \\Delta{t}M^{-1}\\frac{\\partial f}{\\partial v}(x_t,v_t) - (\\Delta{t})^{2}M^{-1}\\frac{\\partial f}{\\partial v}(x_{t},v_{t}))v_{t+1} = v_{t}+\\Delta{t}M^{-1}(f(x_{t},v_{t})-\\frac{\\partial f}{\\partial v}(x_{t},v_{t})v_{t})$$ $\\frac{\\partial f}{\\partial v}$ 与 $\\frac{\\partial f}{\\partial x}$ 的求法见这里。 上面的式子是一个形如,$AX = B$的方程组,$A$维度比较大,而且不一定可逆,一般使用雅克比迭代等方法来解。 中点法中点法是欧拉方法的改进,迭代方法如下: $$x_{t + 1} = x_{t} + \\Delta{t}v_{t + 1}$$ $$v_{t + 1} = v_{t} + \\Delta{t}M^{-1}f(\\frac{x_{t} + x_{t+1}}{2},\\frac{v_{t} + v_{t+1}}{2})$$求解与隐式欧拉相似。 Heun法Heun方法是指改进或修改的显式欧拉方法,或类似的两阶段Runge-Kutta方法。 $$v_{t + 1} = v_{t} + \\Delta{t}\\frac{f(x_t,v_t)}{m}$$ $$x_{t + 1} = x_{t} + \\frac{\\Delta{t}}{2}(v_{t + 1} + v_{t})$$ RK4法经典的Runge Kutta方法: $$x_{t + 1} = x_{t} + \\frac{1}{6}(k_{1}(x)+2k_2(x)+2k_3(x)+k_4(x))$$ $$v_{t + 1} = v_{t} + \\frac{1}{6}(k_{1}(v)+2k_2(v)+2k_3(v)+k_4(v))$$ 这里 $$k_1(x) = \\Delta{t}*v_t, k_1(v) = \\Delta{t}*\\frac{f(x_t,v_t)}{m}$$ $$k_2(x)=\\Delta{t}*(v_t + \\frac{k_1(v)}{2}), k_2(v) = \\Delta{t}*\\frac{f(x_t +\\frac{k_1(x)}{2},v_t + \\frac{k_1(v)}{2})}{m}$$ $$k_3(x)=\\Delta{t}*(v_t + \\frac{k_2(v)}{2}), k_3(v) = \\Delta{t}*\\frac{f(x_t +\\frac{k_2(x)}{2},v_t + \\frac{k_2(v)}{2})}{m}$$ $$k_4(x)=\\Delta{t}*(v_t + k_3(v)), k_4(v) = \\Delta{t}*\\frac{f(x_t +k_3(x),v_t + k_3(v))}{m}$$ 代码实现 Verlet法基本的Verlet积分 $$x_{t + 1} = 2x_{t} - x_{t-1}+ \\Delta{t}v_{t + 1} + (\\Delta t)^{2}\\frac{f(x_t,v_t)}{m}$$ $$v_{t + 1} = \\frac{x_{t+1}-x_{t}}{\\Delta t} $$ 参考隐式欧拉:https://blog.csdn.net/silangquan/article/details/12785001https://zhuanlan.zhihu.com/p/148908332RK4:https://scicomp.stackexchange.com/questions/23929/equation-of-motion-by-rk4-methodJocabi迭代:https://blog.csdn.net/weixin_40327927/article/details/88549879","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"Computer Graphics","slug":"Computer-Graphics","permalink":"https://tomsworkspace.github.io/categories/Computer-Graphics/"},{"name":"Physics engine","slug":"Physics-engine","permalink":"https://tomsworkspace.github.io/categories/Physics-engine/"}],"tags":[{"name":"弹簧质点系统","slug":"弹簧质点系统","permalink":"https://tomsworkspace.github.io/tags/%E5%BC%B9%E7%B0%A7%E8%B4%A8%E7%82%B9%E7%B3%BB%E7%BB%9F/"},{"name":"时间积分","slug":"时间积分","permalink":"https://tomsworkspace.github.io/tags/%E6%97%B6%E9%97%B4%E7%A7%AF%E5%88%86/"}]},{"title":"OpenGL坐标系统","slug":"OpenGL坐标系统","date":"2021-08-08T12:57:28.000Z","updated":"2024-10-11T18:29:55.306Z","comments":true,"path":"2021/08/08/OpenGL坐标系统/","link":"","permalink":"https://tomsworkspace.github.io/2021/08/08/OpenGL%E5%9D%90%E6%A0%87%E7%B3%BB%E7%BB%9F/","excerpt":"","text":"OpenGL坐标系统引用 | Coordinate Systems 在计算机图形学中,利用矩阵的变换来实现物体在各个坐标系中的坐标变换,进而呈现出期望的效果。OpenGL希望在每次顶点着色器运行后,可见的所有顶点都为标准化设备坐标(Normalized Device Coordinate, NDC)。也就是说,每个顶点的x,y,z坐标都应该在 -1.0到1.0 之间,超出这个坐标范围的顶点都将不可见。我们通常会自己设定一个坐标的范围,之后再在顶点着色器中将这些坐标变换为标准化设备坐标。然后将这些标准化设备坐标传入光栅器(Rasterizer),将它们变换为屏幕上的二维坐标或像素。 将坐标变换为标准化设备坐标,接着再转化为屏幕坐标的过程通常是分步进行的,也就是类似于流水线那样子。在流水线中,物体的顶点在最终转化为屏幕坐标之前还会被变换到多个坐标系统(Coordinate System)。将物体的坐标变换到几个过渡坐标系(Intermediate Coordinate System)的优点在于,在这些特定的坐标系统中,一些操作或运算更加方便和容易。图形学中比较重要的总共有5个不同的坐标系统: 局部空间(Local Space,或者称为物体空间(Object Space)) 世界空间(World Space) 观察空间(View Space,或者称为视觉空间(Eye Space)) 裁剪空间(Clip Space) 屏幕空间(Screen Space) 这就是一个顶点在最终被转化为片段之前需要经历的所有不同状态。 你现在可能会对什么是坐标空间,什么是坐标系统感到非常困惑。下面,我们将显示一个整体的图片,之后我们会讲解每个空间的具体功能。 概述为了将坐标从一个坐标系变换到另一个坐标系,我们需要用到几个变换矩阵,最重要的几个分别是模型(Model)、观察(View)、投影(Projection)三个矩阵。我们的顶点坐标起始于局部空间(Local Space),在这里它称为局部坐标(Local Coordinate),它在之后会变为世界坐标(World Coordinate),观察坐标(View Coordinate),裁剪坐标(Clip Coordinate),并最后以屏幕坐标(Screen Coordinate)的形式结束。下面的这张图展示了整个流程以及各个变换过程做了什么: 局部坐标是对象相对于局部原点的坐标,也是物体起始的坐标。 下一步是将局部坐标变换为世界空间坐标,世界空间坐标是处于一个更大的空间范围的。这些坐标相对于世界的全局原点,它们会和其它物体一起相对于世界的原点进行摆放。 接下来我们将世界坐标变换为观察空间坐标,使得每个坐标都是从摄像机或者说观察者的角度进行观察的。 坐标到达观察空间之后,我们需要将其投影到裁剪坐标。裁剪坐标会被处理至-1.0到1.0的范围内,并判断哪些顶点将会出现在屏幕上。 最后,我们将裁剪坐标变换为屏幕坐标,我们将使用一个叫做视口变换(Viewport Transform)的过程。视口变换将位于-1.0到1.0范围的坐标变换到由glViewport函数所定义的坐标范围内。最后变换出来的坐标将会送到光栅器,将其转化为片段。 你可能已经大致了解了每个坐标空间的作用。我们之所以将顶点变换到各个不同的空间的原因是有些操作在特定的坐标系统中才有意义且更方便。例如,当需要对物体进行修改的时候,在局部空间中来操作会更说得通;如果要对一个物体做出一个相对于其它物体位置的操作时,在世界坐标系中来做这个才更说得通,等等。如果我们愿意,我们也可以定义一个直接从局部空间变换到裁剪空间的变换矩阵,但那样会失去很多灵活性。 接下来我们将要更仔细地讨论各个坐标系统。 局部空间局部空间是指物体所在的坐标空间,即对象最开始所在的地方。想象你在一个建模软件(比如说Blender)中创建了一个立方体。你创建的立方体的原点有可能位于(0, 0, 0),即便它有可能最后在程序中处于完全不同的位置。甚至有可能你创建的所有模型都以(0, 0, 0)为初始位置(译注:然而它们会最终出现在世界的不同位置)。所以,你的模型的所有顶点都是在局部空间中:它们相对于你的物体来说都是局部的。 我们一直使用的那个箱子的顶点是被设定在-0.5到0.5的坐标范围中,(0, 0)是它的原点。这些都是局部坐标。 世界空间如果我们将我们所有的物体导入到程序当中,它们有可能会全挤在世界的原点(0, 0, 0)上,这并不是我们想要的结果。我们想为每一个物体定义一个位置,从而能在更大的世界当中放置它们。世界空间中的坐标正如其名:是指顶点相对于(游戏)世界的坐标。如果你希望将物体分散在世界上摆放(特别是非常真实的那样),这就是你希望物体变换到的空间。物体的坐标将会从局部变换到世界空间;该变换是由模型矩阵(Model Matrix)实现的。 模型矩阵是一种变换矩阵,它能通过对物体进行位移、缩放、旋转来将它置于它本应该在的位置或朝向。你可以将它想像为变换一个房子,你需要先将它缩小(它在局部空间中太大了),并将其位移至郊区的一个小镇,然后在y轴上往左旋转一点以搭配附近的房子。你也可以把上一节将箱子到处摆放在场景中用的那个矩阵大致看作一个模型矩阵;我们将箱子的局部坐标变换到场景/世界中的不同位置。 观察空间观察空间经常被人们称之OpenGL的摄像机(Camera)(所以有时也称为摄像机空间(Camera Space)或视觉空间(Eye Space))。观察空间是将世界空间坐标转化为用户视野前方的坐标而产生的结果。因此观察空间就是从摄像机的视角所观察到的空间。而这通常是由一系列的位移和旋转的组合来完成,平移/旋转场景从而使得特定的对象被变换到摄像机的前方。这些组合在一起的变换通常存储在一个观察矩阵(View Matrix)里,它被用来将世界坐标变换到观察空间。在下一节中我们将深入讨论如何创建一个这样的观察矩阵来模拟一个摄像机。 裁剪空间在一个顶点着色器运行的最后,OpenGL期望所有的坐标都能落在一个特定的范围内,且任何在这个范围之外的点都应该被裁剪掉(Clipped)。被裁剪掉的坐标就会被忽略,所以剩下的坐标就将变为屏幕上可见的片段。这也就是裁剪空间(Clip Space)名字的由来。 因为将所有可见的坐标都指定在-1.0到1.0的范围内不是很直观,所以我们会指定自己的坐标集(Coordinate Set)并将它变换回标准化设备坐标系,就像OpenGL期望的那样。 为了将顶点坐标从观察变换到裁剪空间,我们需要定义一个投影矩阵(Projection Matrix),它指定了一个范围的坐标,比如在每个维度上的-1000到1000。投影矩阵接着会将在这个指定的范围内的坐标变换为标准化设备坐标的范围(-1.0, 1.0)。所有在范围外的坐标不会被映射到在-1.0到1.0的范围之间,所以会被裁剪掉。在上面这个投影矩阵所指定的范围内,坐标(1250, 500, 750)将是不可见的,这是由于它的x坐标超出了范围,它被转化为一个大于1.0的标准化设备坐标,所以被裁剪掉了。 如果只是图元(Primitive),例如三角形,的一部分超出了裁剪体积(Clipping Volume),则OpenGL会重新构建这个三角形为一个或多个三角形让其能够适合这个裁剪范围。 由投影矩阵创建的观察箱(Viewing Box)被称为平截头体(Frustum),每个出现在平截头体范围内的坐标都会最终出现在用户的屏幕上。将特定范围内的坐标转化到标准化设备坐标系的过程(而且它很容易被映射到2D观察空间坐标)被称之为投影(Projection),因为使用投影矩阵能将3D坐标投影(Project)到很容易映射到2D的标准化设备坐标系中。 一旦所有顶点被变换到裁剪空间,最终的操作——透视除法(Perspective Division)将会执行,在这个过程中我们将位置向量的x,y,z分量分别除以向量的齐次w分量;透视除法是将4D裁剪空间坐标变换为3D标准化设备坐标的过程。这一步会在每一个顶点着色器运行的最后被自动执行。 在这一阶段之后,最终的坐标将会被映射到屏幕空间中(使用glViewport中的设定),并被变换成片段。 将观察坐标变换为裁剪坐标的投影矩阵可以为两种不同的形式,每种形式都定义了不同的平截头体。我们可以选择创建一个正射投影矩阵(Orthographic Projection Matrix)或一个透视投影矩阵(Perspective Projection Matrix)。 正射投影正射投影矩阵定义了一个类似立方体的平截头箱,它定义了一个裁剪空间,在这空间之外的顶点都会被裁剪掉。创建一个正射投影矩阵需要指定可见平截头体的宽、高和长度。在使用正射投影矩阵变换至裁剪空间之后处于这个平截头体内的所有坐标将不会被裁剪掉。它的平截头体看起来像一个容器: 上面的平截头体定义了可见的坐标,它由由宽、高、近(Near)平面和远(Far)平面所指定。任何出现在近平面之前或远平面之后的坐标都会被裁剪掉。正射平截头体直接将平截头体内部的所有坐标映射为标准化设备坐标,因为每个向量的w分量都没有进行改变;如果w分量等于1.0,透视除法则不会改变这个坐标。 正射投影矩阵直接将坐标映射到2D平面中,即你的屏幕,但实际上一个直接的投影矩阵会产生不真实的结果,因为这个投影没有将透视(Perspective)考虑进去。所以我们需要透视投影矩阵来解决这个问题。 透视投影如果你曾经体验过实际生活给你带来的景象,你就会注意到离你越远的东西看起来更小。这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样: 正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交。这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上: $$out = \\begin{pmatrix} x /w \\ y / w \\ z / w \\end{pmatrix}$$ 顶点坐标的每个分量都会除以它的w分量,距离观察者越远顶点坐标就会越小。这是也是w分量非常重要的另一个原因,它能够帮助我们进行透视投影。最后的结果坐标就是处于标准化设备空间中的。如果你对正射投影矩阵和透视投影矩阵是如何计算的很感兴趣可以看看这篇文章。 在GLM中可以这样创建一个透视投影矩阵: glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f); 同样,glm::perspective所做的其实就是创建了一个定义了可视空间的大平截头体,任何在这个平截头体以外的东西最后都不会出现在裁剪空间体积内,并且将会受到裁剪。一个透视平截头体可以被看作一个不均匀形状的箱子,在这个箱子内部的每个坐标都会被映射到裁剪空间上的一个点。下面是一张透视平截头体的图片: 当使用正射投影时,每一个顶点坐标都会直接映射到裁剪空间中而不经过任何精细的透视除法(它仍然会进行透视除法,只是w分量没有被改变(它保持为1),因此没有起作用)。因为正射投影没有使用透视,远处的物体不会显得更小,所以产生奇怪的视觉效果。由于这个原因,正射投影主要用于二维渲染以及一些建筑或工程的程序,在这些场景中我们更希望顶点不会被透视所干扰。某些如 Blender 等进行三维建模的软件有时在建模时也会使用正射投影,因为它在各个维度下都更准确地描绘了每个物体。下面你能够看到在Blender里面使用两种投影方式的对比: 你可以看到,使用透视投影的话,远处的顶点看起来比较小,而在正射投影中每个顶点距离观察者的距离都是一样的。 组合我们为上述的每一个步骤都创建了一个变换矩阵:模型矩阵、观察矩阵和投影矩阵。一个顶点坐标将会根据以下过程被变换到裁剪坐标: $$V_{clip} = M_{projection} \\cdot M_{view} \\cdot M_{model} \\cdot V_{local}$$ 注意矩阵运算的顺序是相反的(记住我们需要从右往左阅读矩阵的乘法)。最后的顶点应该被赋值到顶点着色器中的gl_Position,OpenGL将会自动进行透视除法和裁剪。所有的这些矩阵就描述了一个物体及外部世界的观察角度,最终决定在屏幕上显示出的效果。 顶点着色器的输出要求所有的顶点都在裁剪空间内,这正是我们刚才使用变换矩阵所做的。OpenGL然后对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL会使用glViewPort内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点(比如是一个800x600的屏幕)。这个过程称为视口变换。 可以从下面的思路来理解每个矩阵做的事情。(1)模型矩阵定义了物体本身的远动状态,包括对它的平移,旋转,缩放等。(2)视图矩阵定义了我们怎么去看这个物体,也就是摄像机怎么去“拍”这个物体以及从什么角度去“拍物体”,这包括对摄像机的一系列的移动和旋转。现实中我们是移动眼睛,即“我动世界不动”,但是在计算机图形学里是通过同时移动所有的物体来实现的,而假设摄像机不动而且处在世界的中心(0,0,0),对着z轴负方向,上方是y轴正向,即“世界动我不动”。这样做的好处是,我们可以很方便为不同的物体计算出这个矩阵,而不用担心相机到底在哪里。(3)透视矩阵则决定了观察者眼睛的参数,即摄像机的参数,我们不可能把整个世界都看在眼里,所以需要定义眼睛的可视范围,在范围外的就不显示在屏幕上了。这个矩阵描述了这个“眼睛”有多大视角(眼睛能睁多大),眼睛离物体多远(太近了看不到,太远了也看不到),是一个有"近大远小"的眼睛呢还是远近一样大的眼睛。最后,通过这三个矩阵,物体就被“映射”到屏幕上了,我们就可以看到物体了。同样的,有了这三个矩阵,我们就可以根据需要来调整相应的矩阵达到让物体"动起来"的效果。比如要让物体动,那就调整模型矩阵;要让摄像机动,那就修改视图矩阵,这实际是通过让所有物体都动起来实现的。 进入3D既然我们知道了如何将3D坐标变换为2D坐标,我们可以开始使用真正的3D物体,而不是枯燥的2D平面了。OpenGL没有自带任何的矩阵和向量的内容,所以我们必须定义自己的数学类和函数。因此使用专门为OpenGL量身定做的数学库,GLM(OpenGL Mathematics)。它是一个只有头文件的库,只需要直接包含头文件即可使用。 我们需要的GLM的大多数功能包括矩阵向量以及相关的运算都可以从下面这3个头文件中找到: #include <glm/glm.hpp>#include <glm/gtc/matrix_transform.hpp>#include <glm/gtc/type_ptr.hpp> 在开始进行3D绘图时,我们首先创建一个模型矩阵。这个模型矩阵包含了位移、缩放与旋转操作,它们会被应用到所有物体的顶点上,以变换它们到全局的世界空间。这个模型矩阵看起来是这样的: glm::mat4 model;//如果使用的是glm 0.9.9及以上版本,这行代码就需要改为:// glm::mat4 trans = glm::mat4(1.0f)model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f)); //旋转model = glm::translate(model, glm::vec3(1.0f, 1.0f, 0.0f)); //位移model = glm::scale(model, glm::vec3(0.5, 0.5, 0.5)); //缩放 假如将这个矩阵乘以一个正方体的每个顶点,那么我们就实现了对他的旋转(绕x轴逆时针旋转55度),位移(位移(1,1,0),缩放(每个轴缩小0.5倍)。 接下来我们需要创建一个观察矩阵。我们想要在场景里面稍微往后移动,以使得物体变成可见的(当在世界空间时,我们位于原点(0,0,0))。要想在场景里面移动,先仔细想一想下面这个句子: 将摄像机向后移动,和将整个场景向前移动是一样的。 这正是观察矩阵所做的,我们以相反于摄像机移动的方向移动整个场景。因为我们想要往后移动,并且OpenGL是一个右手坐标系(Right-handed System),所以我们需要沿着z轴的正方向移动。我们会通过将场景沿着z轴负方向平移来实现。它会给我们一种我们在往后移动的感觉。 !!! important **右手坐标系(Right-handed System)** 按照惯例,OpenGL是一个右手坐标系。简单来说,就是正x轴在你的右手边,正y轴朝上,而正z轴是朝向后方的。想象你的屏幕处于三个轴的中心,则正z轴穿过你的屏幕朝向你。坐标系画起来如下: ![coordinate_systems_right_handed](https://cdn.jsdelivr.net/gh/TOMsworkspace/TOMsworkspace.github.io/2021/08/08/OpenGL坐标系统/figure6.png) 为了理解为什么被称为右手坐标系,按如下的步骤做: - 沿着正y轴方向伸出你的右臂,手指着上方。 - 大拇指指向右方。 - 食指指向上方。 - 中指向下弯曲90度。 如果你的动作正确,那么你的大拇指指向正x轴方向,食指指向正y轴方向,中指指向正z轴方向。如果你用左臂来做这些动作,你会发现z轴的方向是相反的。这个叫做左手坐标系,它被DirectX广泛地使用。注意在标准化设备坐标系中OpenGL实际上使用的是左手坐标系(投影矩阵交换了左右手)。 观察矩阵是这样的: // 注意,我们将矩阵向我们要进行移动场景的反方向移动。glm::mat4 view;//如果使用的是glm 0.9.9及以上版本,这行代码就需要改为:// glm::mat4 trans = glm::mat4(1.0f)view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f)); 这就定义了一个处在(0,0,3),看着(0,0,0),正上方是y轴正方向的一个相机视图矩阵。 最后我们需要做的是定义一个投影矩阵。如果我们希望在场景中使用透视投影,所以像这样声明一个投影矩阵: glm::mat4 projection;projection = glm::perspective(glm::radians(45.0f), screenWidth / screenHeight, 0.1f, 100.0f); 它的第一个参数定义了fov的值,它表示的是视野(Field of View),并且设置了观察空间的大小。如果想要一个真实的观察效果,它的值通常设置为45.0f,但想要一个末日风格的结果你可以将其设置一个更大的值。第二个参数设置了宽高比,由视口的宽除以高所得。第三和第四个参数设置了平截头体的近和远平面。我们通常设置近距离为0.1f,而远距离设为100.0f。所有在近平面和远平面内且处于平截头体内的顶点都会被渲染。 !!! important 当你把透视矩阵的 *near* 值设置太大时(如10.0f),OpenGL会将靠近摄像机的坐标(在0.0f和10.0f之间)都裁剪掉,这会导致一个你在游戏中很熟悉的视觉效果:在太过靠近一个物体的时候你的视线会直接穿过去。 如果我们希望在场景中使用正交投影,那么像这样声明一个投影矩阵: glm::mat4 projection;projection = glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f); 前两个参数指定了平截头体的左右坐标,第三和第四参数指定了平截头体的底部和顶部。通过这四个参数我们定义了近平面和远平面的大小,然后第五和第六个参数则定义了近平面和远平面的距离。这个投影矩阵会将处于这些x,y,z值范围内的坐标变换为标准化设备坐标。 既然我们已经创建了变换矩阵,我们应该将它们传入着色器。首先,让我们在顶点着色器中声明一个uniform变换矩阵然后将它乘以顶点坐标: #version 330 corelayout (location = 0) in vec3 aPos;...uniform mat4 model;uniform mat4 view;uniform mat4 projection;void main(){ // 注意乘法要从右向左读 gl_Position = projection * view * model * vec4(aPos, 1.0); ...} 我们还应该将矩阵传入着色器(这通常在每次的渲染迭代中进行,因为变换矩阵会经常变动): int modelLoc = glGetUniformLocation(ourShader.ID, "model"));glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));... // 观察矩阵和投影矩阵与之类似 Z缓冲OpenGL存储它的所有深度信息于一个Z缓冲(Z-buffer)中,也被称为深度缓冲(Depth Buffer),它允许OpenGL决定何时覆盖一个像素而何时不覆盖。GLFW会自动为你生成这样一个缓冲(就像它也有一个颜色缓冲来存储输出图像的颜色)。深度值存储在每个片段里面(作为片段的z值),当片段想要输出它的颜色时,OpenGL会将它的深度值和z缓冲进行比较,如果当前的片段在其它片段之后,它将会被丢弃,否则将会覆盖。这个过程称为深度测试(Depth Testing),它是由OpenGL自动完成的。 然而,如果我们想要确定OpenGL真的执行了深度测试,首先我们要告诉OpenGL我们想要启用深度测试;它默认是关闭的。我们可以通过glEnable函数来开启深度测试。glEnable和glDisable函数允许我们启用或禁用某个OpenGL功能。这个功能会一直保持启用/禁用状态,直到另一个调用来禁用/启用它。现在我们想启用深度测试,需要开启GL_DEPTH_TEST: glEnable(GL_DEPTH_TEST); 因为我们使用了深度测试,我们也想要在每次渲染迭代之前清除深度缓冲(否则前一帧的深度信息仍然保存在缓冲中)。就像清除颜色缓冲一样,我们可以通过在glClear函数中指定DEPTH_BUFFER_BIT位来清除深度缓冲: glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"OpenGL","slug":"OpenGL","permalink":"https://tomsworkspace.github.io/categories/OpenGL/"},{"name":"Computer Graphics","slug":"Computer-Graphics","permalink":"https://tomsworkspace.github.io/categories/Computer-Graphics/"},{"name":"Physics engine","slug":"Physics-engine","permalink":"https://tomsworkspace.github.io/categories/Physics-engine/"}],"tags":[{"name":"OpenGL","slug":"OpenGL","permalink":"https://tomsworkspace.github.io/tags/OpenGL/"}]},{"title":"Doxygen自动生成文档","slug":"Doxygen自动生成文档","date":"2021-08-06T13:24:50.000Z","updated":"2024-10-11T18:29:55.291Z","comments":true,"path":"2021/08/06/Doxygen自动生成文档/","link":"","permalink":"https://tomsworkspace.github.io/2021/08/06/Doxygen%E8%87%AA%E5%8A%A8%E7%94%9F%E6%88%90%E6%96%87%E6%A1%A3/","excerpt":"","text":"Doxygen 生成文档1.简介 我们在编写代码时一般会写一些注释,在写文档时又会用到这些注释。如果不能直接利用这些注释,就会做很多重复的工作。因此,Doxygen立足于解决这个问题,只要我们在写注释时按一定的格式来写,它就可以将我们在写代码时写的注释转化为各种格式的文档,已支持包括 HTML, LATEX, MAN pages, REF doc, XML, Docbook等多种格式。  很多项目都使用了Doxygen来生成文档。比如:LLVM, CGAL, VTK,glm,Eigen等。  Doxygen生成的流程概述如下: 1.Doxygen  Doxygen能将程序中的特定批注转换成为说明文件。它可以依据程序本身的结构,将程序中按规范注释的批注经过处理生成一个纯粹的参考手册,通过提取代码结构或借助自动生成的包含依赖图(include dependency graphs)、继承图(inheritance diagram)以及协作图(collaboration diagram)来可视化文档之间的关系。它支持多种语言,包括C, C++, python, java, c#, php, Objective-C, Fortran, VHDL, Markdown等。 2.graphviz  Graphviz(Graph Visualization Software)是一个由AT&T实验室启动的开源工具包,用于绘制DOT语言脚本描述的图形。要使用Doxygen生成依赖图、继承图以及协作图,必须先安装graphviz软件。 3.HTML Help WorkShop  微软出品的HTML Help WorkShop是制作CHM文件的最佳工具,它能将HTML文件编译生成CHM文档。Doxygen软件默认生成HTML文件或Latex文件,我们要通过HTML生成CHM文档,需要先安装HTML Help WorkShop软件,并在Doxygen中进行关联。 2. 安装及配置安装及配置,参考官方文档。 3. 如何写注释  要让Doxygen识别你的注释,需要让注释遵循一定的规范。简要的说,Doxygen注释块其实就是在C、C++注释块的基础添加一些额外标识,使Doxygen把它识别出来, 并将它组织到生成的文档中去。在每个代码项中都可以有两类描述:一种就是brief描述,另一种就是detailed。两种都是可选的,但不能同时没有。简述(brief)就是在一行内简述地描述。而详细描述(detailed)则提供更长,更详细的文档。在Doxygen中,主要通过以下方法将注释块标识成详细(detailed)描述:  JavaDoc风格,在C风格注释块开始使用两个星号’*’: /*** ... 描述 ...*/  Qt风格代码注释,即在C风格注释块开始处添加一个叹号’!’: /*!* ... 描述 ...*/  使用连续两个以上C++注释行所组成的注释块, 而每个注释行开始处要多写一个斜杠或写一个叹号: ////// ... 描述 ...///  同样的,简要说明(brief)有也有多种方式标识,这里推荐使用@brief命令强制说明,例如 /** * @brief 简要注释Brief Description. */  注意以下几点: 1.Doxygen并不处理所有的注释,doxygen重点关注与程序结构有关的注释,比如:文件、类、结构、函数、全局变量、宏等注释,而忽略函数内局部变量、代码等的注释。 2.注释应写在对应的函数或变量前面。JavaDoc风格下,自动会把第一个句号"."前的文本作为简要注释,后面的为详细注释。你也可以用空行把简要注释和详细注释分开。注意要设置JAVADOC_AUTOBRIEF或者QT_AUTOBRIEF设为YES。 3.先从文件开始注释,然后是所在文件的全局函数、结构体、枚举变量、命名空间→命名空间中的类→成员函数和成员变量。 4.Doxygen无法为DLL中定义的类导出文档。 3.1 注释实例 下面用一个例子来总结一下常用的注释: #ifndef TEST_H#define TEST_H文件头注释/** * @file test.h * @author author * @email [email protected] * @version V1.0 * @date 08-02-2021 * @license GNU General Public License (GPL) * @brief Universal Synchronous/Asynchronous Receiver/Transmitter(简要注释) * @detail detail(详细描述) * @attention * This file is part of OST. \\n * This program is free software; you can redistribute it and/or modify \\n * it under the terms of the GNU General Public License version 3 as \\n * published by the Free Software Foundation. \\n * You should have received a copy of the GNU General Public License \\n * along with OST. If not, see <http://www.gnu.org/licenses/>. \\n * Unless required by applicable law or agreed to in writing, software \\n * distributed under the License is distributed on an "AS IS" BASIS, \\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \\n * See the License for the specific language governing permissions and \\n * limitations under the License. \\n * \\n * @htmlonly * <span style="font-weight: bold">History</span> * @endhtmlonly * Version|Auther|Date|Describe * ------|----|------|-------- * V3.3|Jones Lee|07-DEC-2017|Create File * <h2><center>&copy;COPYRIGHT 2017 WELLCASA All Rights Reserved.</center></h2> */ 类注释/*** @class <class‐name> [header‐file] [<header‐name]* @brief brief description(简要注释)* @author <list of authors>* @note* detailed description(详细描述)*/class Test{public: /** @brief A enum, with inline docs */(简要注释) enum TEnum { TVal1, /**< enum value TVal1. */ 如果注释在每个成员后面。要在注释段中使用'<'标识。 TVal2, /**< enum value TVal2. */ TVal3 /**< enum value TVal3. */ } *enumPtr, /**< enum pointer. */ 如果注释在每个成员后面。要在注释段中使用'<'标识。 enumVar; /**< enum variable. */(简要注释) /** @brief A constructor. */ (简要注释) Test(); /** @brief A destructor. */ ~Test(); /** @brief a normal member taking two arguments and returning an integer value. */ int testMe(int a,const char *s); 函数/方法注释 /** * @brief can send the message(简要注释) * @param[in] canx : The Number of CAN(参数说明) * @param[in] id : the can id * @param[in] p : the data will be sent * @param[in] size : the data size * @param[in] is_check_send_time : is need check out the time out * @note Notice that the size of the size is smaller than the size of the buffer. * @return(返回值) * +1 Send successfully \\n * -1 input parameter error \\n * -2 canx initialize error \\n * -3 canx time out error \\n * @par Sample * @code * u8 p[8] = {0}; * res_ res = 0; * res = can_send_msg(CAN1,1,p,0x11,8,1); * @endcode */ extern s32 can_send_msg( const CAN_TypeDef * canx, const u32 id, const u8 *p, const u8 size, const u8 is_check_send_time );}#ENDIF //TEST_H 3.2 编辑器插件,自动注释补全  当然,这需要写大量的注释, 这是很烦的工作。尤其是注释之间是存在一些重复性的工作,比如注释相同的代码部分的注释段是格式相近的,比如类的注释,这是不是可以通过编辑器插件来解决呢?   当然,VScode就提供了这样的插件。在VScode里搜Doxygen Documentation Generator这个插件。安装一下,然后简单配置一下注释模板,即各个部分的注释风格和格式。然后就可以像代码补全一样很方便地写注释了。   下面是一些简单用法。比如:生成一个文件的注释,  生成一个方法/函数的注释:  最后再修改一下注释成实际的内容就可以了。非常方便。","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"文档开发","slug":"文档开发","permalink":"https://tomsworkspace.github.io/categories/%E6%96%87%E6%A1%A3%E5%BC%80%E5%8F%91/"}],"tags":[{"name":"Doxygen","slug":"Doxygen","permalink":"https://tomsworkspace.github.io/tags/Doxygen/"}]},{"title":"CMAKE入门","slug":"CMAKE入门","date":"2021-05-24T11:03:56.000Z","updated":"2024-10-11T18:29:55.269Z","comments":true,"path":"2021/05/24/CMAKE入门/","link":"","permalink":"https://tomsworkspace.github.io/2021/05/24/CMAKE%E5%85%A5%E9%97%A8/","excerpt":"","text":"What CMake can do跨平台构建  一套C/C++代码,多平台运行。假设在Windows上, OSX和Linux上使用:Visual Studio, Xcode, Makefile.可以一套代码基于同一个CMAKE即时编译。直接生成项目,不需要额外配置。 VCS友好 当项目出现更新,如添加一个新文件。这个工作如果交给IDE来做,很麻烦。交给CMAKE,只需要一行代码,类似于Makefile做的。 多生成环境支持 CMAKE已经开始支持多种IDE工具,可以直接通过CMAKE生成IED对应的项目,当切换IDE进行开发时,只需要简单一步即可构建。可直接生成VS项目、xcode项目,eclipse项目、各种平台的Makefile等。  CMAKE现已支持如下的IDE及开发环境。可通过 cmake -help 来查看。 全流程支持 从开发到调试,从生成到构建,从编译到测试,从打包到安装全流程覆盖。 HOW TO LEARN CMAKE  官方文档   Microsoft   github cmake-example 跟着例子学   CGold博客  以后再补充","categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"CMAKE","slug":"CMAKE","permalink":"https://tomsworkspace.github.io/categories/CMAKE/"}],"tags":[{"name":"CMAKE","slug":"CMAKE","permalink":"https://tomsworkspace.github.io/tags/CMAKE/"}]},{"title":"C++参数传递方式及区别","slug":"C++参数传递方式及区别","date":"2021-03-05T14:24:31.000Z","updated":"2024-10-11T18:29:55.264Z","comments":true,"path":"2021/03/05/C++参数传递方式及区别/","link":"","permalink":"https://tomsworkspace.github.io/2021/03/05/C++%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92%E6%96%B9%E5%BC%8F%E5%8F%8A%E5%8C%BA%E5%88%AB/","excerpt":"","text":"一、C++参数传递方式 C++自C语言发展而来,继承了C语言中的函数及按值传递和按指针传递的参数传递方式。但是出于效率与安全的综合考虑,C++引入了引用这个语言特性。而引用被设计主要就是为了作为函数参数,这使得函数运行时的变量成为调用时变量的一个别名。这种参数传递的方式成为引用传递。按引用传递允许被调用的函数能够访问调用函数中的变量。  这种参数传递的方式与值传递不同,后者传递的是函数被调用时的变量的值的拷贝;引用传递与指针传递有些类似,但是又不完全相同。 1、值传递 传递的是调用函数时传入变量的一个拷贝。比如一个int数,一个结构体的拷贝。在函数内部对应的变量与调用时传入的变量是两个个体,前者是后者的拷贝。这也就是说,无论在函数内部如何修改按值传递的变量,函数返回时原先的变量不会受到修改。  按值传递的参数传递方式有两个主要的问题:(1)实际传入的参数是调用时参数的拷贝。同样的内容占两份内存,如果变量是基本数据类型那不会占用太大的空间,如果是占用大空间的变量,比如结构体,类对象等,就会造成空间浪费。(2)函数调用返回后对于调用时的参数没有更改,因为是拷贝。但是某些情况下恰恰需要对传入的参数做更改,而且希望更改的变量在调用它的上下文中也生效,比如一个交换两个传入参数值的函数。 2、指针传递 传递的时变量的指针,或者说是这个变量的地址。由于这个函数与被调用的上下文出于同一进程,因此对于指针指向变量的修改会影响到调用函数的上下文。也就是说调用时传入的指针与函数运行时指针指向的是同一个变量,对它的修改也是同步的。  按指针传递也有两个问题:(1)传入参数实际只是一个地址,因此对于指向占大空间的变量时,使用指针传递能节省开销。但是由于传入的是一个地址,因此在使用时需要谨慎,防止因空指针或指针越界导致各种的问题。(2)对于传入指针指向的对象的修改是同步的,因此,可能会对其进行不必要或是意料之外的修改导致未定义的后果。这个问题在使用了const关键字之后得到解决。 3、引用传递 指针传参是传值传参(值为指针指向的地址),是实参传递给形参,两者内存空间不同(指针变量地址),而引用传参传的是同一个对象,可以减少内存消耗。  按引用传递的是同一个对象,因此不存在空指针或指针越界的问题。但是存在对同一个对象预期外的修改,这个问题同样可以通过const关键字来解决。  ","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"参数传递","slug":"参数传递","permalink":"https://tomsworkspace.github.io/tags/%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92/"}]},{"title":"C++中的指针与引用","slug":"C++中的指针与引用","date":"2021-03-03T12:47:52.000Z","updated":"2024-10-11T18:29:55.260Z","comments":true,"path":"2021/03/03/C++中的指针与引用/","link":"","permalink":"https://tomsworkspace.github.io/2021/03/03/C++%E4%B8%AD%E7%9A%84%E6%8C%87%E9%92%88%E4%B8%8E%E5%BC%95%E7%94%A8/","excerpt":"","text":" C/C++中的指针是一个既让人喜欢又恨的特性。使用指针,既方便了代码的编写,但是随之而来的就是各种因空指针、野指针导致的问题。随着语言的发展,C++中出现了引用这个C语言中所没有的特性。因为引用与指针的 “貌合神离” 使得许多人对指针与引用的正确使用一头雾水,摸不着头脑。 一、指针与引用1、指针 指针,是一个特殊的变量,与int、float之类的变量一样存储变量的值。只是这个值比较特殊:它是另一个变量的地址。对于一个不受限定的指针(非const,非volatile),C++支持通过对指针变量的操作来间接访问和修改它存储的地址的值。因为他是一个变量,所以它存在地址空间,也可以通过修改它存储的地址值来让它指向另一个对象。  对于一个不受限的指针,由于允许它不存储任何变量的地址(空指针)。而且支持多级指针。复杂的语义使得往往在使用时出现各种问题,尤其是在指针作为函数的参数和返回值的时候。出于对于安全和效率的考虑,C++出现了引用这个语言特性。 2、引用 简单来说,引用是已定义变量的别名。就如同一个人有几个名字一样,一个大名几个外号一样,无论叫哪个都是在叫。对于别名的读取修改等全部操作也就是对于原变量的操作。就像typedef关键字给类型取别名一样,引用就是给变量取别名。给变量取别名有几条规则: (1)引用被创建的同时必须被初始化。 (2)不能有NULL引用,引用必须与合法的存储单元关联。 (3)一旦引用被初始化,就不能改变引用的关系。  (1)(2)条规则说的是一个东西。给人取外号你得首先要有这么人吧,不能先取一个外号逮着谁谁就叫这个名。(3)的意思是一旦给人取了外号,这外号就跟着你了,直到人没了(变量销毁)。一个外号不能今天是张三,明天是李四吧。那我找这个外号是找张三还是找李四啊。如果没有这条规则的限定就可能出现很复杂的情况了。 二、指针与引用的区别从引用和指针的定义和规则来看: 1、指针的值可以为空,但是引用的值不能为NULL,”并且引用在定义的时候必须初始化“。  由于指针在定义时并不要求必须赋初值,所以指针可能是空(NULL)值;但是引用规则要求它必须在定义时初始化,所以它不能为空值,即不能绑定到空的对象。这使得指针在使用前必须对其进行非空检查,而引用就不需要了。在多重函数调用和返回时往往容易忘记对指针的非空判断而导致程序异常;引用则不需要对其进行检查。引用这个特性最初被引入的主要原因就是用于作为函数的形参。  ”引用在定义时必须初始化”这句话真的没问题么?我觉得是不太严谨的。看下面的例子: struct Test{ int a[5]; int &b;};cout << sizeof(tag) << endl;  这段代码是可以编译通过的,在64位下x86机器下输出结果是32。这个结构体定义了一个引用,但是并未初始化。下面对结构体实例一下: struct Test{ int a[5]; int &b;};Test test;cout << sizeof(tag) << endl;  这段代码是无法编译通过的,错误是没有合适的构造函数可用。因此“定义时必须初始化”这句话应该改成“分配空间时必须初始化”更合适一些。 2、引用只能在定义时被初始化一次,之后不可变;指针在初始后依然可变。  引用初始化后就不可变的作用类似于一个指针常量,即一个指向了对象之后就不能更改使其指向其他的对象的指针。我们都知道,语言特性只是定义于语言编译器的一些特殊规则。 实际上,多数编译器对引用的底层实现就是一个指针常量加上一些语法规则。引用与指针常量的区别就在于指针常量定义时可以不需要初始化,而且指针与引用访问绑定对象的语法规则也不一样,不过这都只是一些编译器层面的区别。 3、指针是一个实体,需要分配内存空间。引用只是变量的别名,”不需要分配内存空间“。  引用把对本身的一切操作都对应到其绑定的对象上,包括取地址运算。所以对引用变量取地址得到的是它绑定对象的地址。实际上,编译器对我们隐藏了引用变量的地址即空间的信息,当对引用变量取地址时返回的是其绑定变量的地址。从这个现象看来,引用变量似乎是不占地址空间的,但是实际上真的是这样么?恐怕并不是。  之前那个例子的输出是32,5个int占20bytes,由于内存对齐,数组要占24个字节,那剩下的8个bytes的空间应该是引用变量所用。所以引用不需要分配空间的说法并不严谨。  再考虑这样一个场景:当一个接受引用参数的函数在实际运行时,传入的变量绑定到一个引用变量上,无论传入的是一个变量还是一个同类型的变量引用,实际上传入的都是变量的地址,那在函数运行的过程中,肯定是需要空间来保存这个地址的。而且在函数返回引用时也是一样。  那是不是引用变量就一定需要空间呢?我觉得也不一定。看下面一段代码: int a =1;int & b = a;cout << a << " " << b << endl;  这里就不需要也没必要再来浪费空间来保存一个引用变量,编译器在编译时就可以直接使用原变量来直接替换这个引用。实际上,标准并没有规定引用要不要占用内存,也没有规定引用具体要怎么实现,具体随编译器。不过,大多数编译器实现是占用了空间的。 4、有多级指针,但是没有多级引用,只能有一级引用。  根据引用的定义,是没有也不需要多级引用的存在的。想一想,你有一个外号了,还要给你的外号取外号有用么,没有必要吧?但是指针的指针就有其用处了,多级指针是有用处的,如定义多维数组。  注意,在C++11中,出现了例如 int && a 的用法,这是C++11引入的新特性 “右值引用” 而不是多级引用。 5、指针和引用的自增运算结果不一样。  指针自增的结果是指向下一个地址,与原本的地址相差一个指针类型的空间大小;引用加一时引用的绑定的变量值加1。 6、sizeof引用得到的是所指向的变量(对象)的大小,而sizeof 指针得到的是指针本身的大小。 7、没有const引用,有const指针。  例如对于int 类型,具体指没有int& const a这种形式,而const int& a是有 的,前者指引用本身即别名不可以改变,这是当然的(引用规则规定),所以没有也不需要这种形式,后者(常量引用或常引用)指引用所指的值不可以改变,这种用法是存在的。  这与指针类似,当const修饰指针类型时。根据关键字出现的地方有指针常量和常量指针以及指向常量的常量指针三种用法。指针常量表示一个指向常量的指针,不可用指针修改指向变量的值,但是可修改指针的值使其指向其它变量;指针常量表示不能修改指针使其指向其它变量,但是可以修改指向变量的值,这与引用相对应;指向常量的指针常量表示不可以修改指针指向的变量也不能修改指针使其指向其它变量,这与常量引用相对应。比较绕,请看下面的代码: //指针//1.常量指针const int * p;int const * p;//2.指针常量int * const p;//3.指向常量的指针常量int const * const p;const int * const p;//引用//1.引用,类似于指针常量int & a; //合法int & const a; //不存在也不需要//2.常量引用,类似于指向常量的指针常量const int & a;int const & a; 8、引用比指针更加安全  这个区别也是来自于引用的规则定义导致的。引用在定义时就与变量绑定了,而指针不一定,指针在定义后没有初始化就是空指针。引用与被引用的变量是同一个地址,编译器隐藏了引用的地址不能对引用本身进行地址操作,这样使地址是不可修改的,使访问更加安全。指针可能导致空指针、野指针、数组越界之类的问题,但引用不会。 三、为什么要有引用 不存在指向空值的引用这个事实,意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。  而指针可能导致空指针、野指针、数组越界之类的问题,但引用不会。但是要注意,在返回引用的函数中,应避免返回局部变量的引用这种用法。局部变量在函数退出时被销毁,返回其引用导致引用绑定到不存在的变量上,这会导致不可预见的错误。 [1]https://www.runoob.com/w3cnote/cpp-difference-between-pointers-and-references.html[2]https://blog.csdn.net/zhengqijun_/article/details/54980769?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328592.13329.16147746419368463&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control[3]https://blog.csdn.net/qq_27678917/article/details/70224813[4]https://blog.csdn.net/superwangxinrui/article/details/80565594?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-2&spm=1001.2101.3001.4242[5]https://blog.csdn.net/weixin_30270889/article/details/97389623?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control&dist_request_id=1328593.13456.16147750581565135&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.control[6]https://blog.csdn.net/qq_39539470/article/details/81273179?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328593.13456.16147750581565135&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control[7]https://blog.csdn.net/wanttifa/article/details/100904422[8]https://blog.csdn.net/gmcxsqjh/article/details/3719552?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control[9]https://blog.csdn.net/vczlyz/article/details/79214334?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=1328592.13326.16147803071270639&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control[10]https://blog.csdn.net/dimengban0741/article/details/101880410?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-2.control[11]https://bbs.csdn.net/topics/320095541","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"指针","slug":"指针","permalink":"https://tomsworkspace.github.io/tags/%E6%8C%87%E9%92%88/"},{"name":"引用","slug":"引用","permalink":"https://tomsworkspace.github.io/tags/%E5%BC%95%E7%94%A8/"}]},{"title":"GCC自带的一些builtin内建函数","slug":"GCC自带的一些builtin内建函数","date":"2021-02-27T10:57:00.000Z","updated":"2024-10-11T18:35:16.515Z","comments":true,"path":"2021/02/27/GCC自带的一些builtin内建函数/","link":"","permalink":"https://tomsworkspace.github.io/2021/02/27/GCC%E8%87%AA%E5%B8%A6%E7%9A%84%E4%B8%80%E4%BA%9Bbuiltin%E5%86%85%E5%BB%BA%E5%87%BD%E6%95%B0/","excerpt":"","text":"一、GCC内建函数 最近在刷 leetcode 的时候遇到了一些以__builtin开头的函数,它们被用在状态压缩相关的题目中特别有用,于是就去了解了一下。  原来这些函数是GCC编译器自带的内建函数。这些__builtin_*形式的内建函数一般是基于不同硬件平台采用专门的硬件指令实现的,因此性能较高。在刷题时可以直接用而不用重复造轮子,尤其是一些涉及到位操作的内建函数真的特别有用。  我对其中涉及到位运算的内建函数挺感兴趣的,于是就去查了一下它们的接口以及具体实现。下一节主要介绍一下几个特别有用的函数。  关于GCC内建函数的更完整的内容可以参见 官方文档。 二、常用的内建函数及实现 针对实现,只实现64位无符号整型的版本。其余的版本依此类推。 1、__builtin_ctz 一共有三个函数,分别适用于不同的输入类型。 int __builtin_ctz (unsigned int x)Returns the number of trailing 0-bits in x, starting at the least significant bit position. If x is 0, the result is undefined.int __builtin_ctzl (unsigned long)Similar to __builtin_ctz, except the argument type is unsigned long.int __builtin_ctzll (unsigned long long)Similar to __builtin_ctz, except the argument type is unsigned long long.  这个函数作用是返回输入数二进制表示从最低位开始(右起)的连续的0的个数;如果传入0则行为未定义。三个函数分别用于unsigned int,unsigned long以及unsigned long long。 实现int __builtin_ctzl(unsigned long x) { for (int i = 0; i != 64; ++i) if (x >> i & 1) return i; return 0;} int __builtin_ctzl(unsigned long x) { int r = 63; x &= ~x + 1; if (x & 0x00000000FFFFFFFF) r -= 32; if (x & 0x0000FFFF0000FFFF) r -= 16; if (x & 0x00FF00FF00FF00FF) r -= 8; if (x & 0x0F0F0F0F0F0F0F0F) r -= 4; if (x & 0x3333333333333333) r -= 2; if (x & 0x5555555555555555) r -= 1; return r;} 2、__builtin_clz 一共有三个函数,分别适用于不同的输入类型。 int __builtin_clz (unsigned int x)Returns the number of leading 0-bits in x, starting at the most significant bit position. If x is 0, the result is undefined.int __builtin_clzl (unsigned long)Similar to __builtin_clz, except the argument type is unsigned long.int __builtin_clzll (unsigned long long)Similar to __builtin_clz, except the argument type is unsigned long long.  这个函数作用是返回输入数二进制表示从最高位开始(左起)的连续的0的个数;如果传入0则行为未定义。三个不同的函数分别用于unsigned int,unsigned long以及unsigned long long。 实现int __builtin_clzl(unsigned long x) { for (int i = 0; i != 64; ++i) if (x >> 63 - i & 1) return i;} int __builtin_clzl(unsigned long x) { int r = 0; if (!(x & 0xFFFFFFFF00000000)) r += 32, x <<= 32; if (!(x & 0xFFFF000000000000)) r += 16, x <<= 16; if (!(x & 0xFF00000000000000)) r += 8, x <<= 8; if (!(x & 0xF000000000000000)) r += 4, x <<= 4; if (!(x & 0xC000000000000000)) r += 2, x <<= 2; if (!(x & 0x8000000000000000)) r += 1, x <<= 1; return r;} 3、__builtin_ffs 一共有三个函数,分别适用于不同的输入类型。 int __builtin_ffs (unsigned int x)Returns one plus the index of the least significant 1-bit of x, or if x is zero, returns zero.int __builtin_ffsl (unsigned long)Similar to __builtin_ffs, except the argument type is unsigned long.int __builtin_ffsll (unsigned long long)Similar to __builtin_ffs, except the argument type is unsigned long long.  这个函数作用是返回输入数二进制表示的最低非0位的下标,下标从1开始计数;如果传入0则返回0。三个不同的函数分别用于unsigned int,unsigned long以及unsigned long long。 实现 除输入0以外,满足 __builtin_ffs(x) = __builtin_ctz(x) + 1 。 int __builtin_clzl(unsigned long x) { if (x == 0) return 0; return __builtin_ctz(x) + 1;} 4、__builtin_popcount 一共有三个函数,分别适用于不同的输入类型。 int __builtin_popcount (unsigned int x)Returns the number of 1-bits in x.int __builtin_popcountl (unsigned long)Similar to __builtin_popcount, except the argument type is unsigned long.int __builtin_popcountll (unsigned long long)Similar to __builtin_popcount, except the argument type is unsigned long long.  这个函数作用是返回输入的二进制表示中1的个数;如果传入0则返回 0 。三个不同的函数分别用于unsigned int,unsigned long以及unsigned long long。 实现int __builtin_popcountl(unsigned long x) { int r = 0; do{ r += x & 1; while (x >>= 1); return r;} int __builtin_popcountl(unsigned long x) { int r = 0; for (; x; x &= x - 1) ++r; return r;} int __builtin_popcountl(unsigned long x) { x = (x & 0x5555555555555555) + (x >> 1 & 0x5555555555555555); x = (x & 0x3333333333333333) + (x >> 2 & 0x3333333333333333); x = (x & 0x0F0F0F0F0F0F0F0F) + (x >> 4 & 0x0F0F0F0F0F0F0F0F); x = (x & 0x00FF00FF00FF00FF) + (x >> 8 & 0x00FF00FF00FF00FF); x = (x & 0x0000FFFF0000FFFF) + (x >> 16 & 0x0000FFFF0000FFFF); x = (x & 0x00000000FFFFFFFF) + (x >> 32 & 0x00000000FFFFFFFF); return x;} int __builtin_popcountl(unsigned long x) { x -= (x >> 1) & 0x5555555555555555; x = (x & 0x3333333333333333) + (x >> 2 & 0x3333333333333333); x = x + (x >> 4) & 0x0F0F0F0F0F0F0F0F; return x * 0x0101010101010101 >> 56;} 5、__builtin_popcount 一共有三个函数,分别适用于不同的输入类型。 int __builtin_parity (unsigned int x)Returns the parity of x, i.e. the number of 1-bits in x modulo 2.int __builtin_parityl (unsigned long)Similar to __builtin_parity, except the argument type is unsigned long.int __builtin_parityll (unsigned long long)Similar to __builtin_parity, except the argument type is unsigned long long.  这个函数作用是返回输入的二进制表示中1的个数的奇偶,也就是输入的二进制中1的个数对2取模的结果。三个不同的函数分别用于unsigned int,unsigned long以及unsigned long long。 实现 根据定义,等价于__builtin_popcount(x) & 1 。 int __builtin_parityl(unsigned long x) { return __builtin_popcountl(x) & 1;} int __builtin_parityl(unsigned long x) { x ^= x >> 1; x ^= x >> 2; x ^= x >> 4; x ^= x >> 8; x ^= x >> 16; x ^= x >> 32; return x & 1;}  这几个函数中都有很巧妙的位运算,大大降低了复杂度。关于位运算的更多例子,可以参考链接 [4] 。 [1]https://gcc.gnu.org/onlinedocs/gcc-4.3.2/gcc/Other-Builtins.html#Other-Builtins[2]https://www.cnblogs.com/justinh/p/7754655.html[3]https://xr1s.me/2018/08/23/gcc-builtin-implementation/[4]http://graphics.stanford.edu/~seander/bithacks.html","categories":[{"name":"GCC","slug":"GCC","permalink":"https://tomsworkspace.github.io/categories/GCC/"}],"tags":[{"name":"GCC","slug":"GCC","permalink":"https://tomsworkspace.github.io/tags/GCC/"},{"name":"builtin函数","slug":"builtin函数","permalink":"https://tomsworkspace.github.io/tags/builtin%E5%87%BD%E6%95%B0/"}]},{"title":"C++中const变量的修改与赋值","slug":"C++中const变量的修改与赋值","date":"2021-02-25T12:26:16.000Z","updated":"2024-10-11T18:29:55.257Z","comments":true,"path":"2021/02/25/C++中const变量的修改与赋值/","link":"","permalink":"https://tomsworkspace.github.io/2021/02/25/C++%E4%B8%ADconst%E5%8F%98%E9%87%8F%E7%9A%84%E4%BF%AE%E6%94%B9%E4%B8%8E%E8%B5%8B%E5%80%BC/","excerpt":"","text":" 出于避免对数据的无意修改的需求,C及C++语言引入了const关键字。与C语言中的const相比,C++中的const具有更丰富的用法。C++中的const除了可用于修饰变量,指针,函数及函数参数,还可用于修饰类对象,类成员,类成员函数等。由于其丰富的用途,往往容易对其产生误解,尤其是与指针用法结合在一起时。下面以一些实例来说明这个问题。 一、const修饰变量1、含义 当使用const修饰普通变量时。如 int const age = 39;//或const int age = 39;  表示const关键字修饰的这个变量是一个常量,不能对其值进行修改。但实际上这句话不是绝对的。根据C++标准,对于修改const变量,属于未定义行为(指行为不可预测的计算机代码),这样一来这个行为的结果取决于各种编译器的具体实现(即不同编译器可能表现不同)。 2、修改const变量 下面来说一下如何修改const修饰的变量。可以使用直接赋值和使用指针来修改其值。如: const int age = 39;//1.直接赋值age = 40;//2.使用指针修改int * p = (int *)&age;*p = 40;cout << "age=" << age << endl;cout << "*p=" << *p << endl;cout << "&age=" << &age << endl;cout << "p=" << (void*)p << endl;cout << "&p=" << &p << endl;  这段代码对于age变量是全局变量还是局部变量结果并不一样。 (1)修改局部变量 程序能正常运行,且常量被修改了,但是有一个问题:输出age的值和*p的值并不相同,age的值还是39,而*p的值是40。而且p确实指向age所在的地址空间。  这是什么原因呢?难道一个地址空间可以存储不同的俩个值?当然不能。这就是C++中的常量折叠:const变量(即常量)值放在编译器的符号表中,计算时编译器直接从表中取值,省去了访问内存的时间,这是编译器进行的优化。age是const变量,编译器对age在预处理的时候就进行了替换。编译器只对const变量的值读取一次。所以打印的是39。age实际存储的值被指针p所改变。但是为什么能改变呢,从其存储地址可以看出来,其存储在堆栈中。 (2)修改全局变量 程序编译通过,但运行时错误。编译器提示age存储的空间不可写,也就是没有写权限,不能修改其值。 原因是age是全局变量,全局变量存储在全局空间,且只有可读属性,无法修改其值。 (3)volatile关键字 由于编译器对const修饰的局部变量进行了优化,使得对于const变量值的读取实际没有进入内存。而在此基础上加上volatile关键字,即告诉编译器该变量属于易变的,不要对此句进行优化,每次计算时要去内存中读取该变量的值,进而避免出现常量折叠的问题。可以这样做: const volatile int age = 39;//1.直接赋值age = 40;//2.使用指针修改int * p = (int *)&age;*p = 40;cout << "age=" << age << endl;cout << "*p=" << *p << endl;cout << "&age=" << &age << endl;cout << "p=" << (void*)p << endl;cout << "&p=" << &p << endl;  这里也有个问题:每种编译器对volatile关键字的支持效果并不一致,有的编译器就直接“不理会”,如VC++6.0编译器[1],使得依然无法避免常量折叠的问题。 (4)强制类型转换 const修饰的变量在C++中被看作是常量,无法修改。但是存在这样的场景:有时候我们需要一个一个值,它在大多数时候是常量,但是在又是又是可以更改的。这时候就需要使用指针来对const变量进行修改。但是在上面的例子中使用到了强制类型转换: const volatile int age = 39;//使用指针修改//不使用强制类型转换int * p = &age; //强制类型转换int * p = (int *)&age; //或int * p = const_cast<int *>(&age); *p = 40;  在这里如果不使用强制类型转换,等于是将一个const int * 类型赋值给一个int * 类型,无法编译通过。必须使用强制类型转换。 二、const 修饰指针 当使用const修饰指针变量时,情况更加复杂起来了。const可以放置在不同的地方,因此具有不同的含义。请看下面一个例子: int age = 39;const int * p = &age;int const * p = &age;int * const p = &age;int * p const = &age;  在这个例子中,一二两种情况是一个意思,表示p是一个指向const的指针;第三种用法表示p是一个常量指针;第四种情况不符合语法。 1、指向常量(const)的指针和常量(const)指针 故名思意:指向常量的指针表示const修饰的指针是指向常量的,不能用这个指针来修改它指向的值,但是可以让该指针指向其他地址。这里就有个问题:age明明是一个变量,为什么可以赋值给一个指向常量(const)的指针?原因是这里发生了隐式的类型转换,&age是一个int * 类型,而p是一个const int * 类型,从int *到const int *是从大范围到小范围,C++进行了隐式的转换,所以能够赋值成功。前一节的从const int * 到int * 是从小范围到一个大范围,无法进行隐式类型转换,所以必须进行强制(显式)类型转换才能赋值成功就是这个原因了。  常量指针表示指针的值是常量,也就是不能修改指针指向的位置,但是可以修改该指针指向的变量的值。  那一个指针能不能既是指向常量的指针又是常量指针呢?这是可以的。例如: int age = 39;const int * const p = &age;  此时的p表示既不能修改它指向的位置的变量的值,又不能使p指向其他位置的变量。 2、修改指向常量的指针 使用如下的例子来修改指向常量的指针: int age = 39;int year = 2021;const int * p = &age;//修改p指向变量的值,编译错误*p = 40;//修改指针p的值,可以修改p = &year;  运行发现确实无法修改p指向的值,编译错误;但是可以修改指针P的值,让它指向另外的变量year。 3、修改常量指针 使用如下的例子来修改常量指针: int age = 39;int year = 2021;int * const p = &age;//修改p指向变量的值,可以修改*p = 40;//修改指针p的值,编译错误p = &year;  运行发现可以修改p指向的值;但是不能修改指针P的值,让它指向另外的变量year。  对于既是既是指向常量的指针又是常量指针,则既无法修改指向的值,又无法修改指针的值,让它指向另外的变量。 int age = 39;int year = 2021;const int * const p = &age;//修改p指向变量的值,编译错误*p = 40;//修改指针p的值,编译错误p = &year; 三、使用const的好处1、可以定义const常量 这样可以避免由于无意间修改数据而导致的编程错误。 2、便于进行类型检查 const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误。 3、为函数重载提供了一个参考 const修饰的函数可以看作是对同名函数的重载。 4、可以节省空间,避免不必要的内存分配 const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象宏一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而宏定义的常量在内存中有若干个拷贝。 5、提高了效率 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期的常量,没有了存储与读内存的操作,使得它的效率也很高。 [1]https://blog.csdn.net/zz460833359/article/details/48917217[2]https://blog.csdn.net/niusi1288/article/details/90710176?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-3&spm=1001.2101.3001.4242[3]https://blog.csdn.net/qq_30968657/article/details/53837022[4]https://blog.csdn.net/Eric_Jo/article/details/4138548?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=1ba4e5e7-1d6a-4d31-9b36-5fcc5bb702fa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-Blog CommendFromMachineLearnPai2-1.control[5]https://blog.csdn.net/Eric_Jo/article/details/4138548?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&dist_request_id=1ba4e5e7-1d6a-4d31-9b36-5fcc5bb702fa&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"Const","slug":"Const","permalink":"https://tomsworkspace.github.io/tags/Const/"}]},{"title":"C++关键字与保留标识符","slug":"C++关键字与保留标识符","date":"2020-11-25T07:15:19.000Z","updated":"2024-10-11T18:29:55.261Z","comments":true,"path":"2020/11/25/C++关键字与保留标识符/","link":"","permalink":"https://tomsworkspace.github.io/2020/11/25/C++%E5%85%B3%E9%94%AE%E5%AD%97%E4%B8%8E%E4%BF%9D%E7%95%99%E6%A0%87%E8%AF%86%E7%AC%A6/","excerpt":"","text":"C++语言关键字和保留标识符关键字 关键字是组成编程语言词汇表的标识符,不能将他们用于其他用途。下表列出了C++所有关键字及,包括C++11,14,17及20标准对于关键字用途的重新定义。 关键字 标准 描述 alignas C++11 用于内存对齐相关 alignof C++11 用于内存对齐相关 asm C++11 用于在C++代码中直接插入汇编语言代码 auto C++98,C++11 C++ 98 中,auto 的作用是让变量成为自动变量(拥有自动的生命周期),但是该作用是多余的,变量默认拥有自动的生命周期。在C++11 中,已经删除了该用法,取而代之的作用是:自动推断变量的类型。 bool C++11 声明布尔类型变量 break C++98 跳出循环语句 case C++98 用于switch分支语句 catch C++11 异常处理,与try一起用于捕获并处理异常 char C++98 声明字符类型 char16_t C++11 声明UTF-16字符集表示的字符类型,要求大到足以表示任何 UTF-16 编码单元(16位)。 char32_t C++11 声明UTF-32字符集表示的字符类型,要求大到足以表示任何 UTF-16 编码单元(32位)。 class C++98,C++11 1)声明类;声明有作用域枚举类型(C++11 起);2)在模板声明中,class 可用于引入类型模板形参与模板模板形参;3)若作用域中存在具有与某个类类型的名字相同的名字的函数或变量,则 class 可附于类名之前以消歧义,这时被用作类型说明符 const C++98 可出现于任何类型说明符中,以指定被声明对象或被命名类型的常量性(constness)。 const_cast C++11 在有不同 cv 限定(const and volatile)的类型间进行类型转换。 constexpr C++11,14,17 constexpr 说明符声明可以在编译时求得函数或变量的值。然后这些变量和函数(若给定了合适的函数实参)可用于编译时生成常量表达式。用于对象或非静态成员函数 (C++14 前)声明的constexpr说明符蕴含const。用于函数声明的 constexpr说明符或static 成员变量 (C++17 起)蕴含inline。若函数或函数模板的任何声明拥有constexpr说明符,则每个声明必须都含有该说明符。 continue C++98 跳出当前循环,开始下一次循环 dealtype C++11,14,17 检查实体的声明类型,或表达式的类型和值类别。对于变量,指定要从其初始化器自动推导出其类型。对于函数,指定要从其return语句推导出其返回类型。(C++14 起)对于非类型模板形参,指定要从实参推导出其类型。(C++17 起) default C++98 用于switch分支语句 delete C++11 解内存分配运算符,与new一起管理动态分配内存;弃置函数,如果取代函数体而使用特殊语法=delete,则该函数被定义为弃置的(deleted)。 do C++98 do-while循环语句 double C++98 声明双精度浮点数类型 dynastic_cast C++11 类型转换运算符,沿继承层级向上、向下及侧向,安全地转换到其他类的指针和引用。 else C++98 if-else条件语句 enum C++98 声明枚举类型 explicit C++11,17,20 1) 指定构造函数或转换函数(C++11 起)或推导指引(C++17起)为显式,即它不能用于隐式转换和复制初始化。2) explicit说明符可以与常量表达式一同使用。当且仅当该常量表达式求值为true时函数为显式(C++20 起)。explicit说明符只可出现于在类定义之内的构造函数或转换函数(C++11 起)的声明说明符序列中。 export C++98,11,20 用于引用文件外模板声明(C++11 前)。不使用并保留该关键词(C++11 起)(C++20 前)。标记一个声明、一组声明或另一模块为当前模块所导出(C++20 起)。 extern C++98 具有外部链接的静态存储期说明符,显式模板实例化声明 false C++11 布尔值假 float C++98 声明单精度的浮点类型 for C++98 for循环 friend C++11 友元声明出现于类体内,并向一个函数或另一个类授予对包含友元声明的类的私有及受保护成员的访问权。 goto C++98 程序跳转到指定的位置 if C++98 if条件语句 inline C++98 声明内联类型 int C++98 声明整形类型 long C++98 声明长整型 mutable C++11 可出现于任何类型说明符(包括声明文法的声明说明符序列)中,以指定被声明对象或被命名类型的常量性(constness)或易变性(volatility)。 namespace C++11 声明名称空间以避免名称冲突 new C++11 分配运算符,与delete一起管理动态分配内存。 noexcept C++11 1)noexcept运算符,进行编译时检查,若表达式声明为不抛出任何异常则返回true。2)noexcept说明符,指定函数是否抛出异常。 nullptr C++11 指针字面量,用于表示空指针 operator C++11 为用户定义类型的操作数重载C++运算符。 private C++11 访问说明符。在class/struct或union的成员说明中,定义其后继成员的可访问性。 protected C++11 访问说明符。在class/struct或union的成员说明中,定义其后继成员的可访问性。 public C++11 访问说明符。在class/struct或union的成员说明中,定义其后继成员的可访问性。 register C++98,17 自动存储期说明符(弃用)。(C++17 前)register关键字请求编译器尽可能的将变量存在CPU内部寄存器中,而不是通过内存寻址访问,以提高效率。不使用并保留该关键词(C++17 起)。 reinterpret_cast C++11 类型转换运算符。通过重新解释底层位模式在类型间转换。 return C++98 函数返回 short C++98 声明短整型数据类型 signed C++98 声明带符号的数据类型 sizeof C++98 返回指向的数据对象或类型所占空间的大小,以字节(byte)为单位 static C++98 声明具有静态存储期和内部链接的命名空间成员,定义具有静态存储期且仅初始化一次的块作用域变量,声明不绑定到特定实例的类成员 static_assert C++11 声明编译时检查的断言 static_cast C++11 类型转换运算符。用隐式和用户定义转换的组合在类型间转换。 struct C++98 声明结构体变量类型 switch C++98 switch分支语句 template C++11 声明模板类型 this C++11 关键字this是一个纯右值表达式,其值是隐式对象形参(在其上调用非静态成员函数的对象)的地址。它能出现于下列语境:1) 在任何非静态成员函数体内,包括成员初始化器列表;2) 在非静态成员函数的声明中,(可选的)cv 限定符(const and volatile)序列之后的任何位置,包括动态异常说明(弃用)、noexcept 说明(C++11)以及尾随返回类型(C++11 起)3) 在默认成员初始化器中。(C++11 起) thread_local C++11 声明属于创建线程私有的线程局部数据变量 throw C++11 抛出异常 true C++11 布尔值真 try C++11 异常处理,与catch一起用于捕获并处理异常 typedef C++98 创建能在任何位置替代(可能复杂的)类型名的别名。 typeid C++11 查询类型的信息。用于必须知晓多态对象的动态类型的场合以及静态类型鉴别。 typename C++11,17 在模板声明中,typename可用作class的代替品,以声明类型模板形参和模板形参(C++17 起)。在模板的声明或定义内,typename可用于声明某个待决的有限定名类型。 union C++98 声明联合体类型变量 unsigned C++98 声明无符号类型变量 using C++11 对命名空间的using指令及对命名空间成员的using声明;对类成员的using声明;类型别名与别名模板声明。(C++11 起) virtual C++11 说明符指定非静态成员函数为虚函数并实现运行时多态。用于声明虚基类。 void C++98 声明无(void)类型的变量,无形参函数的形参列表。 volatile C++98 可出现于任何类型说明符中,以指定被声明对象或被命名类型的易变性(volatility)。 wchar_t C++11 宽字符类型。要求大到足以表示任何受支持的字符编码。 while C++98 do-while循环语句  将所有关键字分一下类大概可以分为如下几类 数据类型,包括auto,bool,char,char16_t,char32_t,class,double,enum,false,float,int,long,nullptr,short,signed,struct,template,true,union,unsigned,void,wchar_t。 存储类别说明符,包括auto,extern,register,static,thread_local, 程序控制,包括asm,break,case,continue,default,do,else,for,goto,if,rturn,switch,while。 类型限定,包括const,mutable,volatile。 类型转换,包括const_cast,dynastic_cast,reinterpret_cast,static_cast,explicit。 异常处理,包括catch,try,throw,noexcept。 内存管理相关,包括new,delete,alignas,alignof,sizeof。 类相关,包括class,explicit,friend,namespace,operator,private,protected,public,template,this,using,virtual。 编译优化相关,包括constexpr,inline,static_assert。 其他,包括export,dealtype,typedef,typeid,typename。。 替代标记 除关键字外,C++有一些运算符能被字母替代,成为替代标记。运算符的替代标记如下表: 标记 含义 标记 含义 and && and_eq &= bitand & bitor | compl ~ not ! not_eq != or || or_eq |= xor ^ xor_eq ^= 保留标识符 还有一些保留标识符。C++语言已经指定了它们的用途或保留它们的使用权,如果你使用这些标识符,后果是不确定的。也就是说,可能导致编译错误、警告、程序不能正确运行或者根本不会导致任何问题。  C++语言保留了库头文件中使用的宏名。如果程序中包含了某个头文件,则不应该将该头文件中定义的宏名用作其他目的。C++语言保留了以两个下划线或下划线和大写字母打头的名称,还将以单个下划线打头的名称保留用作全局变量。C++语言保留了在库头文件中被声明为外部链接性的名称。如函数特征标(名称和参数列表)。 有特殊含义的标识符 C++使用具有特殊意义的标识符来避免新增关键字。这些标识符不是关键字,但用于实现语言功能。编译器根据上下文来判断他们是常规标识符还是用于实现语言功能。例如,virtual,delete,final,override等。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 关键字 保留标识符","slug":"C-关键字-保留标识符","permalink":"https://tomsworkspace.github.io/tags/C-%E5%85%B3%E9%94%AE%E5%AD%97-%E4%BF%9D%E7%95%99%E6%A0%87%E8%AF%86%E7%AC%A6/"}]},{"title":"C++语言标准","slug":"C++语言标准","date":"2020-11-23T11:48:29.000Z","updated":"2024-10-11T18:29:55.267Z","comments":true,"path":"2020/11/23/C++语言标准/","link":"","permalink":"https://tomsworkspace.github.io/2020/11/23/C++%E8%AF%AD%E8%A8%80%E6%A0%87%E5%87%86/","excerpt":"","text":"C++语言的起源 与C语言一样,C++也是在贝尔实验室诞生的,Bjarne Stroustrup于20世纪80年代在这里开发出了这种语言。用他自己的话来说,“C++主要是为了我的朋友和我不必再使用汇编语言、C语言或者其他现代高级语言来编程而设计的。它的主要功能是可以更方便地编写出好程序,让每个程序员更加快乐”。 C++语言标准 美国国家标准委员会(ANSI)在1990年成立委员会,后来国际化标准组织ISO也通过其委员会加入这个行列。他们组成ANSI/ISO组织,专门致力于制定C++标准。  现已发布和计划中的标准如下: C++98/C++03标准 1998年制定出的第一个C++标准ISO/IEC 14882:1998。通常被称为C++98,它不仅描述了已有的C++特性,还对该语言进行了扩展,添加了异常、运行阶段识别符(RTTI)、模板和标准模板库(STL)。2003年,发布了第二个C++标准ISO/IEC 14882:2003。这个版本的是一次技术性修正,对第一版进行了整理、修订错误、减少多义性等,但是没有改变语言特性。这个版本通常被称为C++03。由于没有改变语言特性,一般用C++98表示C++98/C++03。 C++11标准 ISO标准委员会在2001年8月批准了新的标准C++11 ISO/IEC 14882:2011。C++11在C++98的基础上增加了许多新特性。其目标是消除不一致性,让C++学习和使用更加容易。 C++14标准 C++标准第四版,2014年8月18日发布。正式名称为ISO/IEC 14882:2014 。2014年8月18日,ISO组织在其网站上发布文章称:”C++ 作者 Bjarne Stroustrup 称,主要的编译器开发商已经实现了 C++ 14 规格”。C++ 14 是 C++ 11 的增量更新,主要是支持普通函数的返回类型推演,泛型 lambda,扩展的 lambda 捕获,对 constexpr 函数限制的修订,constexpr变量模板化等等。C++14是C++语言的最新标准,正式名称为”International Standard ISO/IEC 14882:2014(E) Programming Language C++”。C++14旨在作为C++11的一个小扩展,主要提供漏洞修复和小的改进。C++14标准的委员会草案(Committee Draft)N3690于2013年5月15日发表。工作草案(Working Draft)N3936已于2014年3月02日完成。最终的投票期结束于2014年8月15日,结果(一致通过)已于8月18日公布。 C++17标准 C++17 是继 C++14 之后,C++ 编程语言 ISO/IEC 标准的下一次修订的非正式名称。而就在昨日,ISO C++ 委员会正式发布了 C++ 17 标准,官方名称为 ISO/IEC 14882:2017。基于 C++ 11,C++ 17 旨在使 C++ 成为一个不那么臃肿复杂的编程语言,以简化该语言的日常使用,使开发者可以更简单地编写和维护代码。 C++20标准 下一代开发中的标准。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"}]},{"title":"gem5运行SPECCPU2017benchmark","slug":"gem5运行SPECCPU2017benchmark","date":"2020-10-11T07:39:06.000Z","updated":"2024-10-11T18:29:55.311Z","comments":true,"path":"2020/10/11/gem5运行SPECCPU2017benchmark/","link":"","permalink":"https://tomsworkspace.github.io/2020/10/11/gem5%E8%BF%90%E8%A1%8CSPECCPU2017benchmark/","excerpt":"","text":"系统环境配置 硬件: CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz 4核 8 线程 内存16G 硬盘 2T HDD+512G SSD  软件: Ubuntu 18.04 LTS GCC/G++/FORTRAN 7.5.0 python 3.7.6 gem5 20.1.0.0 SPEC CPU2017 benchmark   spec cpu2017 由43个benchmark组成,由20个整型和23个浮点benchmark组成。整型和浮点又分别有 吞吐量(Rate)和速度(Speed)两种类别的bench,分别用于测试 CPU的速度和吞吐量。将43个bench分为整型吞吐量(int_rate)、整型速度(int_speed)、浮点吞吐量(fp_rate)、浮点速度(fp_speed)四个类别。下面是对43个bench的具体介绍: SPECrate 2017 Integer SPECspeed 2017 Integer Language KLOC[1] 应用领域 500.perlbench_r 600.perlbench_s C 362 Perl编程语言解释器 502.gcc_r 602.gcc_s C 1304 GUN C编译器 505.mcf_r 605.mcf_s C 3 路径规划 520.omnetpp_r 620.omnetpp_s C++ 134 离散事件模拟-计算机网络 523.xalancbmk_r 623.xalancbmk_s C++ 520 XSLT将XML转化为HTML 525.x264_r 625.x264_s C 96 视频压缩 531.deepsjeng_r 631.deepsjeng_s C++ 10 人工智能:alpha-beta数搜索(国际象棋) 541.leela_r 641.leela_s C++ 21 人工智能:蒙特卡洛述搜索(围棋) 548.exchange2_r 648.exchange2_s Fortran 1 人工智能:递归解决方案生成器(数独) 557.xz_r 657.xz_s C 33 通用数据压缩 SPECrate 2017 Integer SPECspeed 2017 Integer Language KLOC[1] 应用领域 503.bwaves_r 603.bwaves_s Fortran 1 爆炸模型 507.cactuBSSN_r 607.cactuBSSN_s C++ C Fortran 257 物理:相对论 508.namd_r C++ 8 分子动力学 510.parest_r C++ 427 生物医学成像 511.povray_r C++ C 170 光学追踪 519.lbm_r 619.lbm_s C 1 流体动力学 521.wrf_r 621.wrf_s Fortran C 991 天气预报 526.blender_r C++ C 1577 3D动画渲染 527.cam4_r 627.cam4_s Fortran C 407 大气建模 628.pop2_s Fortran C 338 大规模海洋模拟(气候水平) 538.imagick_r 638.imgick_s C 259 图像处理 544.nab_r 644.nab_s C 24 分子动力学 549.fotonik3d_r 649.fotonik3d_s Fortran 14 计算电磁学 554.roms_r 654.roms_s Fortran 210 区域海洋模拟 [1]KLOC=编译时使用的源码长度(包括空格和注释)/1000 编译运行 (1)spec cpu 2017编译  主要按照官方文档的说明。  (2)gem5的安装运行  官方文档  (3)自动化脚本  关于43个benmark的输入数据及命令格式:SPEC CPU2017 commands 其他跟多细节也可参照我之前的文章,在GME5运行SPEC CPU 2006 benchmark CSDN链接 协同仿真 目前已经成功编译并运行大部分bench,但是还有3个bench编译成功但是无法正常运行。具体如下: BENCHMARK 错误原因 510.parest_r An error occurred in line <377> of file <source/base/utilities.cc> in function The violated condition was: cpuinfo The name and call sequence of the exception was: ExcIO() 527.cam4_r At line 869 of file shr_file_mod.fppized.f90 (unit = 98) Fortran runtime error: Cannot open file ‘cpl_modelio.nml’: No such file or directory 627.cam4_s At line 869 of file shr_file_mod.fppized.f90 (unit = 98) Fortran runtime error: Cannot open file ‘cpl_modelio.nml’: No such file or directory 主要问题(1)spec cpu 2017 编译错误 主要可能导致的原因如下: gcc/g++/gfortran 路径错误,从提供的配置文件直接拷贝时需要修改一下编译工具链的路径。 优化选项导致的错误,比如-fno-tree-loop-vectorize优化选项,将其去掉。 (2)protobuf导致的gem5编译错误 主要可能导致的原因如下: protocbuf版本太低或者太高 系统中存在多个版本的protobuf  由于protobuf会被在很多地方采用(如python),所以系统中可能存在多个版本的protocbuf。导致编译时使用了不同版本的库或工具导致编译出错。编译时可以从这个思路排查。可以卸载其他版本的protobuf或者使用源码编译安装。如果实在无法解决问题,可以修改gem5安装目录下的SConstruct文件禁用protobuf组件或卸载protobuf可以使编译通过,这不会影响gem5的主要功能。 (3)panic Unrecognized instruction executedpanic: Unrecognized/invalid instruction executed:{ leg = 0x10, rex = 0, vex/xop = 0x5, op = { type = three byte 0f3a, op = 0x18, }, modRM = 0, sib = 0, immediate = 0, displacement = 0 dispSize = 0}  这个问题出现在使用Gem5运行spec cpu 2017 的 benchmark时,出现错误的原因主要是cpu2017的benchmark编译时生成的代码指令中包含gem5目前不支持的无效指令,很可能是SSE或AVX指令。这个问题可以通过在cpu 2017的配置文件中加上-march选项来生成支持的代码指令。 #vim your-cpu2017-config.cfgdefault=base: # flags for all base OPTIMIZE = -g -O3 -march=x86-64 -fno-unsafe-math-optimizations  参考[1]https://gem5-users.gem5.narkive.com/jO22f7kV/spec2017-on-gem5-se-mode#post5[2]https://www.mail-archive.com/[email protected]/msg28987.html[3]https://www.mail-archive.com/[email protected]/msg14911.html","categories":[{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/categories/gem5/"}],"tags":[{"name":"GEM5","slug":"GEM5","permalink":"https://tomsworkspace.github.io/tags/GEM5/"},{"name":"CPU2017","slug":"CPU2017","permalink":"https://tomsworkspace.github.io/tags/CPU2017/"}]},{"title":"C++多继承时的虚函数表结构","slug":"C++多继承时的虚函数表结构","date":"2020-09-26T09:40:36.000Z","updated":"2024-10-11T18:29:55.265Z","comments":true,"path":"2020/09/26/C++多继承时的虚函数表结构/","link":"","permalink":"https://tomsworkspace.github.io/2020/09/26/C++%E5%A4%9A%E7%BB%A7%E6%89%BF%E6%97%B6%E7%9A%84%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8%E7%BB%93%E6%9E%84/","excerpt":"","text":" C++为了实现运行时的多态,引入了虚函数的概念。为了实现运行时多态的,其底层一般采用虚函数表来实现对虚函数的动态绑定,进而在基类对象的引用或指针在调用同名的虚函数时可以根据引用或指针指向对象的实际类型调用相应的函数。当类的继承关系中没有使用多继承时,对象的虚函数表结构还相对简单;然而继承中出现多集成时,问题就变得复杂起来了。  有如下的一个例子: class A{public: A(int a=0):a(a){} virtual void fun1() { cout << "A::fun1()" <<endl; }private: int a;};class Base1: public A{public : Base1(int b1=1):A(0),b1(b1) {} virtual void func1() { cout<<"Base1::func1" <<endl; } virtual void func2() { cout<<"Base1::func2" <<endl; }private: int b1;};class Base2: public A{public : Base2(int b2=2):A(0),b2(b2) {} virtual void func1() { cout<<"Base2::func1" <<endl; } virtual void func2() { cout<<"Base2::func2" <<endl; }private: int b2;};class Derive : public Base2, public Base1{public : Derive(int c3=3):Base1(1),Base2(2),c3(c3) {} virtual void func1() { cout<<"Derive::func1" <<endl; } virtual void func3() { cout<<"Derive::func3" <<endl; }private: int c3;};  在这个例子中的A、Base1、Base2的虚函数表遵循一般情况下的定义,在每个类的地址空间的头8B(64位)或4B(32位)的空间保存了一个指向该类虚函数表的指针。通过这个指针可以访问其虚函数表。虚函数表的每一个表项保存了指向该类的所有虚函数的函数指针。  理论上是这样,我们把它输出来看。 typedef void (* FUNC) ();typedef unsigned long long ull;//输出虚函数表void PrintVTable (ull* VTable){ cout<<" 虚表地址>"<< VTable<<endl ; int i; for ( i = 0; (VTable[i])!=NULL ; ++i) { cout<<i<<ends ; printf(" 第%d个虚函数地址 :0X%x,->", i , VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout<<endl ;}//输出内存内容void PrintMem(A & obj){ int a = sizeof(obj)/8; for (int i = 0; i < a; ++i) { ull temp =*((ull*)(&obj)+i) ; cout << "整体:" << temp << " 高4B:" << (temp>>32) << " 低4B:" << (unsigned int)temp << endl; }}int main(){ cout << "sizeof A:" << sizeof(A) << endl; cout << "sizeof Base1:" << sizeof(Base1) << endl; cout << "sizeof Base2:" << sizeof(Base2) << endl; cout << "sizeof Derive:" << sizeof(Derive) << endl; A a(1); ull* VTable = (ull*)(*(ull*)(&a)); PrintMem(a); PrintVTable(VTable); Base1 b1(2); VTable =(ull *) (*(ull*)(&b1)); PrintMem(b1); PrintVTable(VTable); return 0;} sizeof A:16sizeof Base1:16sizeof Base2:16sizeof Derive:40整体:93869941914944 高4B:21855 低4B:3431660864整体:93866510254081 高4B:21855 低4B:1虚表地址>0x555fcc8afd40第1个虚函数地址 :0Xcc6aef42,->A::fun1()  对于A,占有16B内存,虚函数表指针占8B,数据成员a实际只占4B,由于内存对齐,所以sizeof A是16 B。对于Base1或Base2.输出结果如下: sizeof Base1:16整体:94892606594304 高4B:22093 低4B:3894123776整体:8589934593 高4B:2 低4B:1虚表地址>0x564de81b9d00第1个虚函数地址 :0Xe7fb9032,->A::fun1()第2个虚函数地址 :0Xe7fb90a6,->Base1::func1第3个虚函数地址 :0Xe7fb90de,->Base1::func2  为什么Base1的长度也是16 B呢?是因为来自A的a和b1 成员分别使用了高4B和低4 B。  这个例子中还出现了菱形继承的情况:Base1和Base2继承自A,而Derive又同时继承自Base1和Base2。同时每个类中都带有虚函数。那这时候Derive是怎么处理自有的虚函数和继承来的虚函数的呢?它的虚函数表又是什么样的结构呢?先输出其内存内容。 cout << "sizeof Derive:" << sizeof(Derive) << endl;Derive d(4);ull * VTable =(ull *) (*(ull*)(&d));PrintMem(d); sizeof Derive:40整体:94560403680304 高4B:22016 低4B:2403691568整体:12884901889 高4B:3 低4B:1整体:94560403680352 高4B:22016 低4B:2403691616整体:8589934593 高4B:2 低4B:1整体:140733193388036 高4B:32767 低4B:4  可以看到, Derive占40B内存,第一个8B是一个虚函数表地址,第二个8B分别是A的a成员和Base2的b2成员,第三个8B又是一个虚函数表地址,第四个8B是又一个A的a成员和Base1的b1成员。最后一个8B,低4B是Derive的c3成员,高四B未使用。 接下来去输出其两个虚函数表: 虚表地址>0x564544793c30第1个虚函数地址 :0X44593286,->A::fun1()第2个虚函数地址 :0X44593476,->Derive::func1第3个虚函数地址 :0X445933de,->Base2::func2第4个虚函数地址 :0X445934b4,->Derive::func3虚表地址>0x564544793c60第1个虚函数地址 :0X44593286,->A::fun1()第2个虚函数地址 :0X445934ad,->Derive::func1第3个虚函数地址 :0X44593332,->Base1::func2  这是还没有使用虚继承的情况,当使用虚继承是又会是什么样呢? class A{public: A(int a=0):a(a){} virtual void fun1() { cout << "A::fun1()" <<endl; }private: int a;};class Base1: virtual public A{public : Base1(int b1=1):A(0),b1(b1) {} virtual void func1() { cout<<"Base1::func1" <<endl; } virtual void func2() { cout<<"Base1::func2" <<endl; }private: int b1;};class Base2: virtual public A{public : Base2(int b2=2):A(0),b2(b2) {} virtual void func1() { cout<<"Base2::func1" <<endl; } virtual void func2() { cout<<"Base2::func2" <<endl; }private: int b2;};class Derive : public Base2, public Base1{public : Derive(int c3=3):Base1(1),Base2(2),c3(c3) {} virtual void func1() { cout<<"Derive::func1" <<endl; } virtual void func3() { cout<<"Derive::func3" <<endl; }private: int c3;}; 这时输出Derive对象的内存及虚函数表: sizeof Derive:48整体:94345236515712 高4B:21966 低4B:1984891776整体:94343251623939 高4B:21966 低4B:3整体:94345236515760 高4B:21966 低4B:1984891824整体:17179869186 高4B:4 低4B:2整体:94345236515800 高4B:21966 低4B:1984891864整体:94343251623937 高4B:21966 低4B:1  可以看到此时Derive占48B,第一个8B是一个虚函数表指针,第二个8B的低4B是Base2的b2成员,高4B未使用,第三个8B又是一个虚函数表指针,第四个8B低4B是Base1的b1成员高4B是Derive的c3成员,第五个8B又是一个虚函数指针,最后一个8B低四位是A的a成员,高4B未使用。注意这里只出现了一个A的副本,符合虚继承的情况。下面是三个虚函数表的内容。 虚表地址>0x5646fc2fcb80第1个虚函数地址 :0Xfc0fc568,->Derive::func1第2个虚函数地址 :0Xfc0fc492,->Base2::func2第3个虚函数地址 :0Xfc0fc5a6,->Derive::func3虚表地址>0x5646fc2fcbb0第1个虚函数地址 :0Xfc0fc59f,->Derive::func1第2个虚函数地址 :0Xfc0fc3d4,->Base1::func2虚表地址>0x5646fc2fcbd8第1个虚函数地址 :0Xfc0fc316,->A::fun1()  但是这里出现了一个我暂时无法解释的现象,在不同的虚函数表对应的同一个虚函数的地址不一样。 环境:Ubuntu18.04 LTS、g++ 5.5.0 。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"多继承","slug":"多继承","permalink":"https://tomsworkspace.github.io/tags/%E5%A4%9A%E7%BB%A7%E6%89%BF/"},{"name":"虚函数表结构","slug":"虚函数表结构","permalink":"https://tomsworkspace.github.io/tags/%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8%E7%BB%93%E6%9E%84/"}]},{"title":"gem5获取bench的访问内存信息","slug":"gem5获取bench的访问内存信息","date":"2020-09-23T11:54:28.000Z","updated":"2024-10-11T18:29:55.307Z","comments":true,"path":"2020/09/23/gem5获取bench的访问内存信息/","link":"","permalink":"https://tomsworkspace.github.io/2020/09/23/gem5%E8%8E%B7%E5%8F%96bench%E7%9A%84%E8%AE%BF%E9%97%AE%E5%86%85%E5%AD%98%E4%BF%A1%E6%81%AF/","excerpt":"","text":"简介 获取gem5的bench的内存访问信息的主要原理来自于gem5提供的调试(debugging)支持.  gem5提供了的调试标志(debug flags)来输出运行仿真的运行时信息。gem5提供了从内存系统到CPU的全系统仿真能力,提供了许多相关的gebug flags 以使我们可以更好的跟踪我们的仿真。 gem5 default debug flags gem5提供了许多以实现的debug flags.可以使用如下命令查看,我的环境(gem5 20.0.0.0 ubuntu 18.04 LTS) build/X86/gem5.opt --debug-help  输出如下: build/X86/gem5.opt --debug-helpBase Flags: Activity: None AddrRanges: None Annotate: State machine annotation debugging AnnotateQ: State machine annotation queue debugging AnnotateVerbose: Dump all state machine annotation details BaseXBar: None Branch: None Bridge: None CCRegs: None CMOS: Accesses to CMOS devices Cache: None CacheComp: None CachePort: None CacheRepl: None CacheTags: None CacheVerbose: None Checker: None Checkpoint: None ClockDomain: None CoherentXBar: None CommMonitor: None Commit: None CommitRate: None Config: None Context: None CxxConfig: None DMA: None DMACopyEngine: None DRAM: None DRAMPower: None DRAMSim2: None DRAMState: None DVFS: None DebugPrintf: None Decode: None Decoder: Decoder debug output DirectedTest: None DiskImageRead: None DiskImageWrite: None DistEthernet: None DistEthernetCmd: None DistEthernetPkt: None Drain: None DynInst: None ElasticTrace: None Ethernet: None EthernetCksum: None EthernetDMA: None EthernetData: None EthernetDesc: None EthernetEEPROM: None EthernetIntr: None EthernetPIO: None EthernetSM: None Event: None ...... 在仿真中使用debug flags 再命令中加上对应的option –debug-flags 并指定要使用的flag就好了,比如: build/X86/gem5.opt --debug-flags=Exec configs/learning_gem5/part1/simple.py | head -n 50 添加一个debug flags  gem5支持自己定义一个flag。具体方法如下: (1)将要定义的flag包含到Sconscript文件中 DebugFlag('Your flags neme')``` &emsp;通过在SConscript文件中声明该标志,将自动生成一个C++头文件,使我们可以使用调试标志。头文件在debug目录中,并且具有与我们在SConscript文件中声明的名称相同的名称(和大小写)。因此,我们需要在计划使用debug flag的任何文件中包含自动生成的头文件。(2)在要使用的地方包含头文件```c++#include "debug/Your flag name.hh" 然后在代码中使用,运行时需要重新编译。 DPRINTF(Your debug name, "Created the hello object\\n");  这里的DPRINTF是一个输出宏,类似于std::out , 不过带了一个debug flag参数。这样可以将输出与不同的对象绑定,使用不同的flag可以调试不同的对象。  类似于DPRINTF的输出宏还有很多,他们都定义在src/base/trace.hh 中。 src/base/trace.hh:223#define DTRACE(x) ;#define DDUMP(x, data, count) ;#define DPRINTF(x, ...) ;#define DPRINTFS(x, ...) ;#define DPRINTFR(...);#define DDUMPN(data, count) ;#define DPRINTFN(...) ;#define DPRINTFNR(...) ;#define DPRINTF_UNCONDITIONAL(x, ...); (3)运行时查看  在运行时在option 中加入–debug-flags = Your debug flag 输出与该flag关联的调试信息。 内存访问MemoryAccess flag MemoryAccess是一个用来输出仿真时bench访问内存信息的debug flag,可以在运行仿真时加入–debug-flags=MemoryAccess来获取bench的内存访问信息。  这个flags使用于如下的c++文件中 src/mem/abstract_mem.cc src/mem/abstract_mem.cc:357DPRINTF(MemoryAccess, "%s from %s of size %i on address %#x %c\\n", label, sys->getMasterName(pkt->req->masterId()), size, pkt->getAddr(), pkt->req->isUncacheable() ? 'U' : 'C');  可以修改这里的代码,获得需要的信息。比如只输出 DPRINTF(MemoryAccess,"%i %s %#x\\n",curTick(), label, pkt->getAddr());  获取对应的内存访问信息。记住每次修改后必须重新编译才能生效。","categories":[{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/categories/gem5/"}],"tags":[{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/tags/gem5/"},{"name":"benchmark","slug":"benchmark","permalink":"https://tomsworkspace.github.io/tags/benchmark/"},{"name":"trace","slug":"trace","permalink":"https://tomsworkspace.github.io/tags/trace/"},{"name":"MemoryAccess","slug":"MemoryAccess","permalink":"https://tomsworkspace.github.io/tags/MemoryAccess/"}]},{"title":"gem5运行SPECCPU2006benchmark","slug":"gem5运行SPECCPU2006benchmark","date":"2020-09-22T13:55:33.000Z","updated":"2024-10-11T18:29:55.309Z","comments":true,"path":"2020/09/22/gem5运行SPECCPU2006benchmark/","link":"","permalink":"https://tomsworkspace.github.io/2020/09/22/gem5%E8%BF%90%E8%A1%8CSPECCPU2006benchmark/","excerpt":"","text":"gem5运行SPECCPU 2006benchmark前言 最近在做实验,准备在GEM5中运行SPECCPU 2006的benchmark。 系统环境配置 硬件: CPU I5-4500 4核 内存8G 硬盘 1T 软件: Ubuntu 18.04 LTS GCC/G++5.5 python2.7 gem5 20.0.0.3 编译安装 GEM5基本安装运行  SPEC CPU 2006 benchmark基本安装运行  SPEC runspec命令该命令是用来编译和运行spec cpu 2006的。 协同仿真目前已编译成功并运行的benchmark int benchmark language success(true or false) 400.perlbench C true 401.bzip2 C true 403.gcc C true 429.mcf C true 445.gobmk C true 456.hmmer C true 458.sjeng C true 462.libquantum C true 464.h264ref C true 471.omnetpp C++ true 473.astar C++ true 483.xalancbmk C++ true fp benchmark language success(true or false) 410.bwaves Fortran true 416.gamess Fortran true 433.milc C true 434.zeusmp Fortran true 435.gromacs C/Fortran true 436.cactusADM C/Fortran true 437.leslie3d Fortran true 444.namd C++ true 447.dealII C++ false 450.soploex C++ true 453.povray C++ true 454.calculix C/Fortran true 459.GemsFDTD Fortran true 465.tonto Fortran true 470.lbm C true 481.wrf C/Fortran true 482.sphinx3 C true gem5 SE/FS 仿真模式gem5有系统调用仿真模式(System Call Emulation (SE) Mode)和全系统仿真模式(Full System (FS) Mode)。可以支持从CPU到内外存的全系统体系结构仿真。你可以根据自己的需要对GEM5源码做相应的扩展,然后运行SE/FS仿真。 运行仿真的格式# <gem5 binary> [gem5 options] <simulation script> [script options] 每个仿真包括一个基于特定体系结构编译的gem5二进制和一个仿真python script,以及相应的options。 gem5 Options使用”gem5 binary -h”查看options,例如: # build/X86/gem5.opt -hUsage===== gem5.opt [gem5 options] script.py [script options]gem5 is copyrighted software; use the --copyright option for details.Options=======--version show programm's version number and exit--help, -h show this help message and exit--build-info, -B Show build information--copyright, -C Show full copyright information--readme, -R Show the readme--outdir=DIR, -d DIR Set the output directory to DIR [Default: m5out]--redirect-stdout, -r Redirect stdout (& stderr, without -e) to file--redirect-stderr, -e Redirect stderr to file--stdout-file=FILE Filename for -r redirection [Default: simout]--stderr-file=FILE Filename for -e redirection [Default: simerr]--listener-mode={on,off,auto} Port (e.g., gdb) listener mode (auto: Enable if running interactively) [Default: auto]--listener-loopback-only Port listeners will only accept connections over the loopback device--interactive, -i Invoke the interactive interpreter after running the script--pdb Invoke the python debugger before running the script--path=PATH[:PATH], -p PATH[:PATH] Prepend PATH to the system path when invoking the script--quiet, -q Reduce verbosity--verbose, -v Increase verbosity... Script Optionsgem5已经提供了很多实例的运行脚本,存在$gem5path/config/example/路径下,其中包括运行SE和FS模式的脚本se.py和fs.py。下面是se.py的options。 Usage: se.py [options]Options: -h, --help show this help message and exit -n NUM_CPUS, --num-cpus=NUM_CPUS --sys-voltage=SYS_VOLTAGE Top-level voltage for blocks running at system power supply --sys-clock=SYS_CLOCK Top-level clock for blocks running at system speed --list-mem-types List available memory types --mem-type=MEM_TYPE type of memory to use --mem-channels=MEM_CHANNELS number of memory channels --mem-ranks=MEM_RANKS number of memory ranks per channel --mem-size=MEM_SIZE Specify the physical memory size (single memory) --enable-dram-powerdown Enable low-power states in DRAMCtrl --mem-channels-intlv=MEM_CHANNELS_INTLV Memory channels interleave --memchecker --external-memory-system=EXTERNAL_MEMORY_SYSTEM use external ports of this port_type for caches --tlm-memory=TLM_MEMORY use external port for SystemC TLM cosimulation --caches --l2cache --num-dirs=NUM_DIRS --num-l2caches=NUM_L2CACHES --num-l3caches=NUM_L3CACHES --l1d_size=L1D_SIZE --l1i_size=L1I_SIZE --l2_size=L2_SIZE --l3_size=L3_SIZE --l1d_assoc=L1D_ASSOC --l1i_assoc=L1I_ASSOC --l2_assoc=L2_ASSOC --l3_assoc=L3_ASSOC --cacheline_size=CACHELINE_SIZE --ruby ...... -c CMD, --cmd=CMD The binary to run in syscall emulation mode. -o OPTIONS, --options=OPTIONS The options to pass to the binary, use " " around the entire string -e ENV, --env=ENV Initialize workload environment from text file. -i INPUT, --input=INPUT Read stdin from a file. --output=OUTPUT Redirect stdout to a file. --errout=ERROUT Redirect stderr to a file. ...... spec cpu2006静态编译spec cpu 2006benchmark 使用命令runspec来进行编译和运行。 runspec命令使用一个以.cfg结尾的配置文件来对benchmark进行编译。默认在$spec安装目录/config/ 下提供了一些配置文件的实例,可以根据需要对其进行修改。 要使spec cpu 2006的bench能在gem5里运行,需要对每个bench进行静态编译。对于配置文件主要做如下的更改: ## Base is low optdefault=base=default=default:COPTIMIZE = -O2 -fno-strict-aliasingCXXOPTIMIZE = -O2 -fno-strict-aliasingFOPTIMIZE = -O2 -fno-strict-aliasing改为COPTIMIZE = -O2 -fno-strict-aliasing -staticCXXOPTIMIZE = -O2 -fno-strict-aliasing -staticFOPTIMIZE = -O2 -fno-strict-aliasing -static 运行一个例子runspec运行 编译并运行一个bench,以401.bzip2为例 # 编译perlbenchrunspec --config=xxx.cfg --action=build --tune=base bzip2#运行perlbenchrunspec --config=xxx.cfg --size=ref -I --tune=base bzip2 这里的xxx.cfg是对应的配置文件,指定了包括编译和运行SPEC CPU的环境参数。还可以使用int,fp,all来替代bzip2编译或运行整型bench,浮点或所有bench。 二进制运行当编译一个bench会在对应目录下生成一个run子文件夹,里面包含编译生成的可执行文件和对应的输入。想了解每个bench 到底做了什么可以直接运行这个可执行文件。以bzip2为例 cd $PATH_TO_YOUR_SPEC_CPU2006_INSTALL/benchspec/CPU2006/401. bzip2/run/run__base.amd64-m64-gcc43-nn.0000/./bzip2_base.amd64-m64-gcc43-nn control 对于不同的bench,有不同的运行输入,具体可见官方说明或安装目录下每个bench下的Docs子文件夹。要使这些bench在gem5中被正确地执行,指定正确的输入是很重要的。否则即使能跑起来也没有正确的输入。使用runspec运行bench 只需要使用–size选项来指定输入就好了。 GEM5运行接下来在gem5中运行一个bench,还是以bzip2为例。使用config/example/se.py作为配置文件。命令很长,将其写成shell 脚本。 #!/bin/bash#run_bzip2.shgem5path=/home/tom/gem5spec2006path=$gem5path/cpu2006/benchspec/CPU2006outdir=/home/tom/Desktop/spec2006Tracebench=401.bzip2ben_suffix=run/run_base.amd64-m64-gcc43-nn.0000/exe=bzip_base.amd64-m64-gcc43-nn$gem5path/build/X86/gem5.opt \\--debug-flags=MemoryAccess \\--outdir=$outdir --debug-file=401.bzip2.out \\$gem5path/configs/example/se.py \\-c "$spec2006path/$bench/$ben_suffix/$exe" \\-o "" \\-i "$spec2006path/$bench/$ben_suffix/control" \\--cpu-nums=1 \\--cpu-type=TimingSimpleCPU \\--caches \\--cacheline_size=64 \\--l1d_size=64kB --l1i_size=32kB --l1i_assoc=8 --l1d_assoc=8 \\--l2cache --l2_size=2MB --l2_assoc=8 \\--l3_size=32MB --l3_assoc=4 \\--mem-size=4096MB 其中的几个参数解释如下: -c 指定gem5运行的二进制文件 -o 运行二进制文件的选项 -i 二进制文件的输入文件,把文件当成输入 要想运行其余的bench,主要改动这三个选项。 自动化脚本显然,对于每个bench都写一个shell太麻烦了,每个bench还需要指定不同的输入,而且没办法同时运行个bench进行多线程仿真。考虑用python脚本来解决这个问题。参考了网上的做法,但是都是将bench当成单线程来模拟,并没有提供多线程的解决方案。参考现有方案,在此基础上写了一个同时支持单线程和多线程的版本。 配置文件spec06_config.py首先,从config/example/se.py配置文件中复制一个配置文件spec_config.py。并对其进行更改。 # Copyright (c) 2012-2013 ARM Limited# All rights reserved.## The license below extends only to copyright in the software and shall# not be construed as granting a license to any other intellectual# property including but not limited to intellectual property relating# to a hardware implementation of the functionality of the software# licensed hereunder. You may use the software subject to the license# terms below provided that you ensure that this notice is replicated# unmodified and in its entirety in all distributions of the software,# modified or unmodified, in source code or in binary form.## Copyright (c) 2006-2008 The Regents of The University of Michigan# All rights reserved.## Redistribution and use in source and binary forms, with or without# modification, are permitted provided that the following conditions are# met: redistributions of source code must retain the above copyright# notice, this list of conditions and the following disclaimer;# redistributions in binary form must reproduce the above copyright# notice, this list of conditions and the following disclaimer in the# documentation and/or other materials provided with the distribution;# neither the name of the copyright holders nor the names of its# contributors may be used to endorse or promote products derived from# this software without specific prior written permission.## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.# Simple test script## "m5 test.py"from __future__ import print_functionfrom __future__ import absolute_importimport optparseimport sysimport osimport m5from m5.defines import buildEnvfrom m5.objects import *from m5.params import NULLfrom m5.util import addToPath, fatal, warnaddToPath('../')from ruby import Rubyfrom common import Optionsfrom common import Simulationfrom common import CacheConfigfrom common import CpuConfigfrom common import ObjectListfrom common import MemConfigfrom common.FileSystemConfig import config_filesystemfrom common.Caches import *from common.cpu2000 import *import spec06_benchmarks# ...snip...parser = optparse.OptionParser()Options.addCommonOptions(parser)Options.addSEOptions(parser)# NAVIGATE TO THIS POINT# ...snip...parser.add_option("-b", "--benchmark", type="string", default="", help="The SPEC benchmark to be loaded.")parser.add_option("--benchmark_stdout", type="string", default="", help="Absolute path for stdout redirection for the benchmark.")parser.add_option("--benchmark_stderr", type="string", default="", help="Absolute path for stderr redirection for the benchmark.")def get_processes(options): """Interprets provided options and returns a list of processes""" multiprocesses = [] outputs = [] errouts = [] workloads = options.benchmark.split(';') if options.benchmark_stdout != "": outputs = options.benchmark_stdout.split(';') elif options.output != "": outputs = options.output.split(';') if options.benchmark_stderr != "": errouts = options.benchmark_stderr.split(';') elif options.errout != "": errouts = options.errout.split(';') idx = 0 for wrkld in workloads: if wrkld: print('Selected SPEC_CPU2006 benchmark') if wrkld == 'perlbench': print('--> perlbench') process = spec06_benchmarks.perlbench elif wrkld == 'bzip2': print('--> bzip2') process = spec06_benchmarks.bzip2 elif wrkld == 'gcc': print '--> gcc' process = spec06_benchmarks.gcc elif wrkld == 'bwaves': print '--> bwaves' process = spec06_benchmarks.bwaves elif wrkld == 'gamess': print '--> gamess' process = spec06_benchmarks.gamess elif wrkld == 'mcf': print '--> mcf' process = spec06_benchmarks.mcf elif wrkld == 'milc': print '--> milc' process = spec06_benchmarks.milc elif wrkld == 'zeusmp': print '--> zeusmp' process = spec06_benchmarks.zeusmp elif wrkld == 'gromacs': print '--> gromacs' process = spec06_benchmarks.gromacs elif wrkld == 'cactusADM': print '--> cactusADM' process = spec06_benchmarks.cactusADM elif wrkld == 'leslie3d': print '--> leslie3d' process = spec06_benchmarks.leslie3d elif wrkld == 'namd': print '--> namd' process = spec06_benchmarks.namd elif wrkld == 'gobmk': print '--> gobmk' process = spec06_benchmarks.gobmk elif wrkld == 'dealII': print '--> dealII' process = spec06_benchmarks.dealII elif wrkld == 'soplex': print '--> soplex' process = spec06_benchmarks.soplex elif wrkld == 'povray': print '--> povray' process = spec06_benchmarks.povray elif wrkld == 'calculix': print '--> calculix' process = spec06_benchmarks.calculix elif wrkld == 'hmmer': print '--> hmmer' process = spec06_benchmarks.hmmer elif wrkld == 'sjeng': print '--> sjeng' process = spec06_benchmarks.sjeng elif wrkld == 'GemsFDTD': print '--> GemsFDTD' process = spec06_benchmarks.GemsFDTD elif wrkld == 'libquantum': print '--> libquantum' process = spec06_benchmarks.libquantum elif wrkld == 'h264ref': print '--> h264ref' process = spec06_benchmarks.h264ref elif wrkld == 'tonto': print '--> tonto' process = spec06_benchmarks.tonto elif wrkld== 'lbm': print '--> lbm' process = spec06_benchmarks.lbm elif wrkld == 'omnetpp': print '--> omnetpp' process = spec06_benchmarks.omnetpp elif wrkld == 'astar': print '--> astar' process = spec06_benchmarks.astar elif wrkld == 'wrf': print '--> wrf' process = spec06_benchmarks.wrf elif wrkld == 'sphinx3': print '--> sphinx3' process = spec06_benchmarks.sphinx3 elif wrkld== 'xalancbmk': print '--> xalancbmk' process = spec06_benchmarks.xalancbmk elif wrkld == 'specrand_i': print '--> specrand_i' process = spec06_benchmarks.specrand_i elif wrkld == 'specrand_f': print '--> specrand_f' process = spec06_benchmarks.specrand_f else: print "No recognized SPEC2006 benchmark selected! Exiting." sys.exit(1) process.cwd = os.getcwd() if len(outputs) > idx: process.output = outputs[idx] if len(errouts) > idx: process.errout = errouts[idx] multiprocesses.append(process) idx += 1 else: print >> sys.stderr, "Need --benchmark switch to specify SPEC CPU2006 workload. Exiting!\\n" sys.exit(1) if options.smt: assert(options.cpu_type == "DerivO3CPU") return multiprocesses, idx else: return multiprocesses, 1#parser = optparse.OptionParser()#Options.addCommonOptions(parser)#Options.addSEOptions(parser)if '--ruby' in sys.argv: Ruby.define_options(parser)(options, args) = parser.parse_args()if args: print("Error: script doesn't take any positional arguments") sys.exit(1)multiprocesses, numThreads = get_processes(options)(CPUClass, test_mem_mode, FutureClass) = Simulation.setCPUClass(options)CPUClass.numThreads = numThreads# Check -- do not allow SMT with multiple CPUsif options.smt and options.num_cpus > 1: fatal("You cannot use SMT with multiple CPUs!")np = options.num_cpussystem = System(cpu = [CPUClass(cpu_id=i) for i in range(np)], mem_mode = test_mem_mode, mem_ranges = [AddrRange(options.mem_size)], cache_line_size = options.cacheline_size, workload = NULL)if numThreads > 1: system.multi_thread = True# Create a top-level voltage domainsystem.voltage_domain = VoltageDomain(voltage = options.sys_voltage)# Create a source clock for the system and set the clock periodsystem.clk_domain = SrcClockDomain(clock = options.sys_clock, voltage_domain = system.voltage_domain)# Create a CPU voltage domainsystem.cpu_voltage_domain = VoltageDomain()# Create a separate clock domain for the CPUssystem.cpu_clk_domain = SrcClockDomain(clock = options.cpu_clock, voltage_domain = system.cpu_voltage_domain)# If elastic tracing is enabled, then configure the cpu and attach the elastic# trace probe#if options.elastic_trace_en:# CpuConfig.config_etrace(CPUClass, system.cpu, options)# All cpus belong to a common cpu_clk_domain, therefore running at a common# frequency.for cpu in system.cpu: cpu.clk_domain = system.cpu_clk_domainif ObjectList.is_kvm_cpu(CPUClass) or ObjectList.is_kvm_cpu(FutureClass): if buildEnv['TARGET_ISA'] == 'x86': system.kvm_vm = KvmVM() for process in multiprocesses: process.useArchPT = True process.kvmInSE = True else: fatal("KvmCPU can only be used in SE mode with x86")# Sanity checkif options.simpoint_profile: if not ObjectList.is_noncaching_cpu(CPUClass): fatal("SimPoint/BPProbe should be done with an atomic cpu") if np > 1: fatal("SimPoint generation not supported with more than one CPUs")for i in range(np): if options.smt: system.cpu[i].workload = multiprocesses elif len(multiprocesses) == 1: system.cpu[i].workload = multiprocesses[0] else: system.cpu[i].workload = multiprocesses[i] if options.simpoint_profile: system.cpu[i].addSimPointProbe(options.simpoint_interval) if options.checker: system.cpu[i].addCheckerCpu() if options.bp_type: bpClass = ObjectList.bp_list.get(options.bp_type) system.cpu[i].branchPred = bpClass() if options.indirect_bp_type: indirectBPClass = \\ ObjectList.indirect_bp_list.get(options.indirect_bp_type) system.cpu[i].branchPred.indirectBranchPred = indirectBPClass() system.cpu[i].createThreads()if options.ruby: Ruby.create_system(options, False, system) assert(options.num_cpus == len(system.ruby._cpu_ports)) system.ruby.clk_domain = SrcClockDomain(clock = options.ruby_clock, voltage_domain = system.voltage_domain) for i in range(np): ruby_port = system.ruby._cpu_ports[i] # Create the interrupt controller and connect its ports to Ruby # Note that the interrupt controller is always present but only # in x86 does it have message ports that need to be connected system.cpu[i].createInterruptController() # Connect the cpu's cache ports to Ruby system.cpu[i].icache_port = ruby_port.slave system.cpu[i].dcache_port = ruby_port.slave if buildEnv['TARGET_ISA'] == 'x86': system.cpu[i].interrupts[0].pio = ruby_port.master system.cpu[i].interrupts[0].int_master = ruby_port.slave system.cpu[i].interrupts[0].int_slave = ruby_port.master system.cpu[i].itb.walker.port = ruby_port.slave system.cpu[i].dtb.walker.port = ruby_port.slaveelse: MemClass = Simulation.setMemClass(options) system.membus = SystemXBar() system.system_port = system.membus.slave CacheConfig.config_cache(options, system) MemConfig.config_mem(options, system) config_filesystem(system, options)if options.wait_gdb: for cpu in system.cpu: cpu.wait_for_remote_gdb = Trueroot = Root(full_system = False, system = system)Simulation.run(options, root, system, FutureClass) bench输入然后写一个脚本解决bench的输入问题,并在spec06_config.py中使用。 ##spec06_benchmarks.pyimport m5from m5.objects import *# These three directory paths are not currently used.# gem5_dir = '<FULL_PATH_TO_YOUR_GEM5_INSTALL>'# spec_dir = '<FULL_PATH_TO_YOUR_SPEC_CPU2006_INSTALL>'# out_dir = '<FULL_PATH_TO_DESIRED_OUTPUT_DIRECTORY>'GEM5_DIR='/home/tom/gem5'#<PATH_TO_YOUR_GEM5_INSTALL> # Install location of gem5SPEC_DIR='/home/tom/cpu2006/benchspec/CPU2006/'dir_suffix = '/run/run_base_ref_amd64-m64-gcc43-nn.0000/'exe_suffix = '_base.amd64-m64-gcc43-nn'# temp# binary_dir = spec_dir# data_dir = spec_dir# 400.perlbenchperlbench = Process(pid=400) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'400.perlbench'+dir_suffixperlbench.executable = fullpath+'perlbench' + exe_suffix# TEST CMDS# perlbench.cmd = [perlbench.executable] + ['-I.', fullpath+'/lib',fullpath+ 'attrs.pl']# REF CMDSperlbench.cmd = [perlbench.executable] + ['-I'+fullpath+'/lib', fullpath+'/checkspam.pl', '2500', '5', '25', '11', '150', '1', '1', '1', '1']# perlbench.cmd = [perlbench.executable] + ['-I'+fullpath+'/lib',fullpath+ 'diffmail.pl', '4', '800', '10', '17', '19', '300']# perlbench.cmd = [perlbench.executable] + ['-I'+fullpath+'/lib',fullpath+ 'splitmail.pl', '1600', '12', '26', '16', '4500']# perlbench.output = out_dir+'perlbench.out'# 401.bzip2bzip2 = Process(pid=401) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'401.bzip2'+dir_suffixbzip2.executable = fullpath+'bzip2' + exe_suffix# TEST CMDS# bzip2.cmd = [bzip2.executable] + [fullpath+'input.program', '5']# REF CMDSbzip2.cmd = [bzip2.executable] + [fullpath+'input.source', '280']# bzip2.cmd = [bzip2.executable] + [fullpath+'chicken.jpg', '30']# bzip2.cmd = [bzip2.executable] + [fullpath+'liberty.jpg', '30']# bzip2.cmd = [bzip2.executable] + [fullpath+'input.program', '280']# bzip2.cmd = [bzip2.executable] + [fullpath+'text.html', '280']# bzip2.cmd = [bzip2.executable] + [fullpath+'input.combined', '200']# bzip2.output = out_dir + 'bzip2.out'# 403.gccgcc = Process(pid=403) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'403.gcc'+dir_suffixgcc.executable = fullpath+'gcc' + exe_suffix# TEST CMDS# gcc.cmd = [gcc.executable] + [fullpath+'cccp.i', '-o',fullpath+ 'cccp.s']# REF CMDSgcc.cmd = [gcc.executable] + [fullpath+'166.i', '-o', fullpath+'166.s']# gcc.cmd = [gcc.executable] + [fullpath+'200.i', '-o',fullpath+ '200.s']# gcc.cmd = [gcc.executable] + [fullpath+'c-typeck.i', '-o',fullpath+ 'c-typeck.s']# gcc.cmd = [gcc.executable] + [fullpath+'cp-decl.i', '-o',fullpath+ 'cp-decl.s']# gcc.cmd = [gcc.executable] + [fullpath+'expr.i', '-o',fullpath+ 'expr.s']# gcc.cmd = [gcc.executable] + [fullpath+'expr2.i', '-o',fullpath+ 'expr2.s']# gcc.cmd = [gcc.executable] + [fullpath+'g23.i', '-o',fullpath+ 'g23.s']# gcc.cmd = [gcc.executable] + [fullpath+'s04.i', '-o',fullpath+ 's04.s']# gcc.cmd = [gcc.executable] + [fullpath+'scilab.i', '-o',fullpath+ 'scilab.s']# gcc.output = out_dir + 'gcc.out'# 410.bwavesbwaves = Process(pid=410) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'410.bwaves'+dir_suffixbwaves.executable = fullpath+'bwaves' + exe_suffix# TEST CMDS# bwaves.cmd = [bwaves.executable]# REF CMDSbwaves.cmd = [bwaves.executable]# bwaves.output = out_dir + 'bwaves.out'# 416.gamessgamess = Process(pid=416) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'416.gamess'+dir_suffixgamess.executable = fullpath+'gamess' + exe_suffix# TEST CMDS# gamess.cmd = [gamess.executable]# gamess.input = fullpath+'exam29.config'# REF CMDSgamess.cmd = [gamess.executable]gamess.input = fullpath+'cytosine.2.config'# gamess.cmd = [gamess.executable]# gamess.input = fullpath+'h2ocu2+.gradient.config'# gamess.cmd = [gamess.executable]# gamess.input = fullpath+'triazolium.config'# gamess.output = out_dir + 'gamess.out'# 429.mcfmcf = Process(pid=429) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'429.mcf'+dir_suffixmcf.executable = fullpath+'mcf' + exe_suffix# TEST CMDS# mcf.cmd = [mcf.executable] + [fullpath+'inp.in']# REF CMDSmcf.cmd = [mcf.executable] + [fullpath+'inp.in']# mcf.output = out_dir + 'mcf.out'# 433.milcmilc = Process(pid=433) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'433.milc'+dir_suffixmilc.executable = fullpath+'milc' + exe_suffix# TEST CMDS# milc.cmd = [milc.executable]# milc.input = fullpath+'su3imp.in'# REF CMDSmilc.cmd = [milc.executable]milc.input = fullpath+'su3imp.in'# milc.output = out_dir + 'milc.out'# 434.zeusmpzeusmp = Process(pid=434) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'434.zeusmp'+dir_suffixzeusmp.executable = fullpath+'zeusmp' + exe_suffix# TEST CMDS# zeusmp.cmd = [zeusmp.executable]# REF CMDSzeusmp.cmd = [zeusmp.executable]# zeusmp.output = out_dir + 'zeusmp.out'# 435.gromacsgromacs = Process(pid=435) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'435.gromacs'+dir_suffixgromacs.executable = fullpath+'gromacs' + exe_suffix# TEST CMDS# gromacs.cmd = [gromacs.executable] + ['-silent','-deffnm',fullpath+ 'gromacs', '-nice','0']# REF CMDSgromacs.cmd = [gromacs.executable] + ['-silent', '-deffnm', fullpath+'gromacs', '-nice', '0']# gromacs.output = out_dir + 'gromacs.out'# 436.cactusADMcactusADM = Process(pid=436) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'436.cactusADM'+dir_suffixcactusADM.executable = fullpath+'cactusADM' + exe_suffix# TEST CMDS# cactusADM.cmd = [cactusADM.executable] + [fullpath+'benchADM.par']# REF CMDScactusADM.cmd = [cactusADM.executable] + [fullpath+'benchADM.par']# cactusADM.output = out_dir + 'cactusADM.out'# 437.leslie3dleslie3d = Process(pid=437) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'437.leslie3d'+dir_suffixleslie3d.executable = fullpath+'leslie3d' + exe_suffix# TEST CMDS# leslie3d.cmd = [leslie3d.executable]# leslie3d.input = fullpath+'leslie3d.in'# REF CMDSleslie3d.cmd = [leslie3d.executable]leslie3d.input = fullpath+'leslie3d.in'# leslie3d.output = out_dir + 'leslie3d.out'# 444.namdnamd = Process(pid=444) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'444.namd'+dir_suffixnamd.executable = fullpath+'namd' + exe_suffix# TEST CMDS# namd.cmd = [namd.executable] + ['--input', fullpath+'namd.input', '--output', fullpath+'namd.out', '--iterations', '1']# REF CMDSnamd.cmd = [namd.executable] + ['--input', fullpath+'namd.input', '--output', fullpath+'namd.out', '--iterations', '38']# namd.output = out_dir + 'namd.out'# 445.gobmkgobmk = Process(pid=445) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'445.gobmk'+dir_suffixgobmk.executable = fullpath+'gobmk' + exe_suffix# TEST CMDS# gobmk.cmd = [gobmk.executable] + ['--quiet','--mode', 'gtp']# gobmk.input = fullpath+'dniwog.tst'# REF CMDSgobmk.cmd = [gobmk.executable] + ['--quiet', '--mode', 'gtp']gobmk.input = fullpath+'13x13.tst'# gobmk.cmd = [gobmk.executable] + ['--quiet','--mode', 'gtp']# gobmk.input = fullpath+'nngs.tst'# gobmk.cmd = [gobmk.executable] + ['--quiet','--mode', 'gtp']# gobmk.input = fullpath+'score2.tst'# gobmk.cmd = [gobmk.executable] + ['--quiet','--mode', 'gtp']# gobmk.input = fullpath+'trevorc.tst'# gobmk.cmd = [gobmk.executable] + ['--quiet','--mode', 'gtp']# gobmk.input = fullpath+'trevord.tst'# gobmk.output = out_dir + 'gobmk.out'# 447.dealII####### NOT WORKING #########dealII = Process(pid=447) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'447.dealII'+dir_suffixdealII.executable = fullpath+'dealII' + exe_suffix# TEST CMDS####### NOT WORKING #########dealII.cmd = [gobmk.executable]+['8']# REF CMDS####### NOT WORKING ########## dealII.output = out_dir + 'dealII.out'# 450.soplexsoplex = Process(pid=450) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'450.soplex'+dir_suffixsoplex.executable = fullpath+'soplex' + exe_suffix# TEST CMDS# soplex.cmd = [soplex.executable] + ['-m10000',fullpath+ 'test.mps']# REF CMDSsoplex.cmd = [soplex.executable] + ['-m45000', fullpath+'pds-50.mps']# soplex.cmd = [soplex.executable] + ['-m3500', fullpath+'ref.mps']# soplex.output = out_dir + 'soplex.out'# 453.povraypovray = Process(pid=453) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'453.povray'+dir_suffixpovray.executable = fullpath+'povray' + exe_suffix# TEST CMDS# povray.cmd = [povray.executable] + [fullpath+'SPEC-benchmark-test.ini']# REF CMDSpovray.cmd = [povray.executable] + [fullpath+'SPEC-benchmark-ref.ini']# povray.output = out_dir + 'povray.out'# 454.calculixcalculix = Process(pid=454) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'454.calculix'+dir_suffixcalculix.executable = fullpath+'calculix' + exe_suffix# TEST CMDS# calculix.cmd = [calculix.executable] + ['-i', 'beampic']# REF CMDScalculix.cmd = [calculix.executable] + ['-i', 'hyperviscoplastic']# calculix.output = out_dir + 'calculix.out'# 456.hmmerhmmer = Process(pid=456) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'456.hmmer'+dir_suffixhmmer.executable = fullpath+'hmmer' + exe_suffix# TEST CMDS# hmmer.cmd = [hmmer.executable] + ['--fixed', '0', '--mean', '325', '--num', '45000', '--sd', '200', '--seed', '0', fullapth+'bombesin.hmm']# REF CMDShmmer.cmd = [hmmer.executable] + [fullpath+'nph3.hmm', fullpath+'swiss41']# hmmer.cmd = [hmmer.executable] + ['--fixed', '0', '--mean', '500', '--num', '500000', '--sd', '350', '--seed', '0', fullpath+'retro.hmm']# hmmer.output = out_dir + 'hmmer.out'# 458.sjengsjeng = Process(pid=458) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'458.sjeng'+dir_suffixsjeng.executable = fullpath+'sjeng' + exe_suffix# TEST CMDS# sjeng.cmd = [sjeng.executable] + [fullpath+'test.txt']# REF CMDSsjeng.cmd = [sjeng.executable] + [fullpath+'ref.txt']# sjeng.output = out_dir + 'sjeng.out'# 459.GemsFDTDGemsFDTD = Process(pid=459) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'459.GemsFDTD'+dir_suffixGemsFDTD.executable = fullpath+'GemsFDTD' + exe_suffix# TEST CMDS# GemsFDTD.cmd = [GemsFDTD.executable]# REF CMDSGemsFDTD.cmd = [GemsFDTD.executable]# GemsFDTD.output = out_dir + 'GemsFDTD.out'# 462.libquantumlibquantum = Process(pid=462) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'462.libquantum'+dir_suffixlibquantum.executable = fullpath+'libquantum' + exe_suffix# TEST CMDS# libquantum.cmd = [libquantum.executable] + ['33','5']# REF CMDS [UPDATE 10/2/2015]: Sparsh Mittal has pointed out the correct input for libquantum should be 1397 and 8, not 1297 and 8. Thanks!libquantum.cmd = [libquantum.executable] + ['1397', '8']# libquantum.output = out_dir + 'libquantum.out'# 464.h264refh264ref = Process(pid=464) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'464.h264ref'+dir_suffixh264ref.executable = fullpath+'h264ref' + exe_suffix# TEST CMDS# h264ref.cmd = [h264ref.executable] + ['-d', fullpath+'foreman_test_encoder_baseline.cfg']# REF CMDSh264ref.cmd = [h264ref.executable] + ['-d', fullpath+'foreman_ref_encoder_baseline.cfg']# h264ref.cmd = [h264ref.executable] + ['-d', fullpath+'foreman_ref_encoder_main.cfg']# h264ref.cmd = [h264ref.executable] + ['-d', fullpath+'sss_encoder_main.cfg']# h264ref.output = out_dir + 'h264ref.out'# 465.tontotonto = Process(pid=465) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'465.tonto'+dir_suffixtonto.executable = fullpath+'tonto' + exe_suffix# TEST CMDS# tonto.cmd = [tonto.executable]# REF CMDStonto.cmd = [tonto.executable]# tonto.output = out_dir + 'tonto.out'# 470.lbmlbm = Process(pid=470) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'470.lbm'+dir_suffixlbm.executable = fullpath+'lbm' + exe_suffix# TEST CMDS# lbm.cmd = [lbm.executable] + ['20', fullpath+'reference.dat', '0', '1', fullpath+'100_100_130_cf_a.of']# REF CMDSlbm.cmd = [lbm.executable] + ['300', fullpath+'reference.dat', '0', '0', fullpath+'100_100_130_ldc.of']# lbm.output = out_dir + 'lbm.out'# 471.omnetppomnetpp = Process(pid=471) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'471.omnetpp'+dir_suffixomnetpp.executable = fullpath+'omnetpp' + exe_suffix# TEST CMDS# omnetpp.cmd = [omnetpp.executable] + [fullpath+'omnetpp.ini']# REF CMDSomnetpp.cmd = [omnetpp.executable] + [fullpath+'omnetpp.ini']# omnetpp.output = out_dir + 'omnetpp.out'# 473.astarastar = Process(pid=473) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'473.astar'+dir_suffixastar.executable = fullpath+'astar' + exe_suffix# TEST CMDS# astar.cmd = [astar.executable] + [fullpath+'lake.cfg']# REF CMDSastar.cmd = [astar.executable] + [fullpath+'rivers.cfg']# astar.output = out_dir + 'astar.out'# 481.wrfwrf = Process(pid=481) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'481.wrf'+dir_suffixwrf.executable = fullpath+'wrf' + exe_suffix# TEST CMDS# wrf.cmd = [wrf.executable]# REF CMDSwrf.cmd = [wrf.executable]# wrf.output = out_dir + 'wrf.out'# 482.sphinx3sphinx3 = Process(pid=482) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'482.sphinx3'+dir_suffixsphinx3.executable = fullpath+'sphinx_livepretend' + exe_suffix# TEST CMDS# sphinx3.cmd = [sphinx3.executable] + [fullpath+'ctlfile', fullpath, fullpath+'args.an4']# REF CMDSsphinx3.cmd = [sphinx3.executable] + [fullpath+'ctlfile', fullpath, fullpath+'args.an4']# sphinx3.output = out_dir + 'sphinx3.out'# 483.xalancbmk######## NOT WORKING ###########xalancbmk = Process(pid=483) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'483.xalancbmk'+dir_suffixxalancbmk.executable = fullpath+'Xalan' + exe_suffix# TEST CMDS######## NOT WORKING ###########xalancbmk.cmd = [xalancbmk.executable] + ['-v',fullpath+'test.xml',fullpath+'xalanc.xsl']# REF CMDS######## NOT WORKING ############ xalancbmk.output = out_dir + 'xalancbmk.out'# 998.specrandspecrand_i = Process(pid=998) # Update June 7, 2017: This used to be LiveProcess()fullpath=SPEC_DIR+'998.specrand'+dir_suffixspecrand_i.executable = fullpath+'specrand' + exe_suffix# TEST CMDS# specrand_i.cmd = [specrand_i.executable] + ['324342', '24239']# REF CMDSspecrand_i.cmd = [specrand_i.executable] + ['1255432124', '234923']# specrand_i.output = out_dir + 'specrand_i.out'# 999.specrandspecrand_f = Process(pid=999) # Update June 7, 2017: This used to be LiveProces using std::in;fullpath=SPEC_DIR+'999.specrand'+dir_suffixspecrand_f.executable = fullpath+'specrand' + exe_suffix# TEST CMDS# specrand_f.cmd = [specrand_f.executable] + ['324342', '24239']# REF CMDSspecrand_f.cmd = [specrand_f.executable] + ['1255432124', '234923']# specrand_f.output = out_dir + 'specrand_f.out' 修改之后,可以通过 cd $PATH_TO_YOUR_GEM5_INSTALL build/ARCH/gem5.opt [gem5 options] config/example/spe06_config.py [script options] --benchmark=YOUR_BENCH 指定对应的bench来运行。为了使运行更简单。分别为运行写了单线程和多线程版本。 单线程运行#!/bin/bash## run_spec2006.sh## This program is free software: you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.## This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## You should have received a copy of the GNU General Public License# along with this program. If not, see <http://www.gnu.org/licenses/>.############ DIRECTORY VARIABLES: MODIFY ACCORDINGLY #############GEM5_DIR=/home/tom/gem5#<PATH_TO_YOUR_GEM5_INSTALL> # Install location of gem5SPEC_DIR=/home/tom/cpu2006#<PATH_TO_YOUR_SPEC_CPU2006_INSTALL> # Install location of your SPEC2006 benchmarksARCH=X86#YOUR gem5 arch##################################################################ARGC=$# # Get number of arguments excluding arg0 (the script itself). Check for help message condition.if [[ "$ARGC" != 2 ]]; then # Bad number of arguments. echo "run_gem5_alpha_spec06_benchmark.sh Copyright (C) 2014 Mark Gottscho" echo "This program comes with ABSOLUTELY NO WARRANTY; for details see <http://www.gnu.org/licenses/>." echo "This is free software, and you are welcome to redistribute it under certain conditions; see <http://www.gnu.org/licenses/> for details." echo "" echo "This script runs a single gem5 simulation of a single SPEC CPU2006 benchmark for Alpha ISA." echo "" echo "USAGE: run_gem5_alpha_spec06_benchmark.sh <BENCHMARK> <OUTPUT_DIR>" echo "EXAMPLE: ./run_gem5_alpha_spec06_benchmark.sh bzip2 /FULL/PATH/TO/output_dir" echo "" echo "A single --help help or -h argument will bring this message back." exitfi# Get command line input. We will need to check these.BENCHMARK=$1 # Benchmark name, e.g. bzip2OUTPUT_DIR=$2 # Directory to place run output. Make sure this exists!RUN_SUFFIX=run/run_base_ref_amd64-m64-gcc43-nn.0000######################### BENCHMARK CODENAMES ####################PERLBENCH_CODE=400.perlbenchBZIP2_CODE=401.bzip2GCC_CODE=403.gccBWAVES_CODE=410.bwavesGAMESS_CODE=416.gamessMCF_CODE=429.mcfMILC_CODE=433.milcZEUSMP_CODE=434.zeusmpGROMACS_CODE=435.gromacsCACTUSADM_CODE=436.cactusADMLESLIE3D_CODE=437.leslie3dNAMD_CODE=444.namdGOBMK_CODE=445.gobmkDEALII_CODE=447.dealIISOPLEX_CODE=450.soplexPOVRAY_CODE=453.povrayCALCULIX_CODE=454.calculixHMMER_CODE=456.hmmerSJENG_CODE=458.sjengGEMSFDTD_CODE=459.GemsFDTDLIBQUANTUM_CODE=462.libquantumH264REF_CODE=464.h264refTONTO_CODE=465.tontoLBM_CODE=470.lbmOMNETPP_CODE=471.omnetppASTAR_CODE=473.astarWRF_CODE=481.wrfSPHINX3_CODE=482.sphinx3XALANCBMK_CODE=483.xalancbmkSPECRAND_INT_CODE=998.specrandSPECRAND_FLOAT_CODE=999.specrand################################################################### Check BENCHMARK input#################### BENCHMARK CODE MAPPING ######################BENCHMARK_CODE="none"if [[ "$BENCHMARK" == "perlbench" ]]; then BENCHMARK_CODE=$PERLBENCH_CODEfiif [[ "$BENCHMARK" == "bzip2" ]]; then BENCHMARK_CODE=$BZIP2_CODEfiif [[ "$BENCHMARK" == "gcc" ]]; then BENCHMARK_CODE=$GCC_CODEfiif [[ "$BENCHMARK" == "bwaves" ]]; then BENCHMARK_CODE=$BWAVES_CODEfiif [[ "$BENCHMARK" == "gamess" ]]; then BENCHMARK_CODE=$GAMESS_CODEfiif [[ "$BENCHMARK" == "mcf" ]]; then BENCHMARK_CODE=$MCF_CODEfiif [[ "$BENCHMARK" == "milc" ]]; then BENCHMARK_CODE=$MILC_CODEfiif [[ "$BENCHMARK" == "zeusmp" ]]; then BENCHMARK_CODE=$ZEUSMP_CODEfiif [[ "$BENCHMARK" == "gromacs" ]]; then BENCHMARK_CODE=$GROMACS_CODEfiif [[ "$BENCHMARK" == "cactusADM" ]]; then BENCHMARK_CODE=$CACTUSADM_CODEfiif [[ "$BENCHMARK" == "leslie3d" ]]; then BENCHMARK_CODE=$LESLIE3D_CODEfiif [[ "$BENCHMARK" == "namd" ]]; then BENCHMARK_CODE=$NAMD_CODEfiif [[ "$BENCHMARK" == "gobmk" ]]; then BENCHMARK_CODE=$GOBMK_CODEfiif [[ "$BENCHMARK" == "dealII" ]]; then # DOES NOT WORK BENCHMARK_CODE=$DEALII_CODEfiif [[ "$BENCHMARK" == "soplex" ]]; then BENCHMARK_CODE=$SOPLEX_CODEfiif [[ "$BENCHMARK" == "povray" ]]; then BENCHMARK_CODE=$POVRAY_CODEfiif [[ "$BENCHMARK" == "calculix" ]]; then BENCHMARK_CODE=$CALCULIX_CODEfiif [[ "$BENCHMARK" == "hmmer" ]]; then BENCHMARK_CODE=$HMMER_CODEfiif [[ "$BENCHMARK" == "sjeng" ]]; then BENCHMARK_CODE=$SJENG_CODEfiif [[ "$BENCHMARK" == "GemsFDTD" ]]; then BENCHMARK_CODE=$GEMSFDTD_CODEfiif [[ "$BENCHMARK" == "libquantum" ]]; then BENCHMARK_CODE=$LIBQUANTUM_CODEfiif [[ "$BENCHMARK" == "h264ref" ]]; then BENCHMARK_CODE=$H264REF_CODEfiif [[ "$BENCHMARK" == "tonto" ]]; then BENCHMARK_CODE=$TONTO_CODEfiif [[ "$BENCHMARK" == "lbm" ]]; then BENCHMARK_CODE=$LBM_CODEfiif [[ "$BENCHMARK" == "omnetpp" ]]; then BENCHMARK_CODE=$OMNETPP_CODEfiif [[ "$BENCHMARK" == "astar" ]]; then BENCHMARK_CODE=$ASTAR_CODEfiif [[ "$BENCHMARK" == "wrf" ]]; then BENCHMARK_CODE=$WRF_CODEfiif [[ "$BENCHMARK" == "sphinx3" ]]; then BENCHMARK_CODE=$SPHINX3_CODEfiif [[ "$BENCHMARK" == "xalancbmk" ]]; then # DOES NOT WORK BENCHMARK_CODE=$XALANCBMK_CODEfiif [[ "$BENCHMARK" == "specrand_i" ]]; then BENCHMARK_CODE=$SPECRAND_INT_CODEfiif [[ "$BENCHMARK" == "specrand_f" ]]; then BENCHMARK_CODE=$SPECRAND_FLOAT_CODEfi# Sanity checkif [[ "$BENCHMARK_CODE" == "none" ]]; then echo "Input benchmark selection $BENCHMARK did not match any known SPEC CPU2006 benchmarks! Exiting." exit 1fi################################################################### Check OUTPUT_DIR existenceif [[ ! -d "$OUTPUT_DIR" ]]; then echo "Output directory $OUTPUT_DIR does not exist! Exiting." exit 1fiRUN_DIR=$SPEC_DIR/benchspec/CPU2006/$BENCHMARK_CODE/$RUN_SUFFIX # Run directory for the selected SPEC benchmarkSCRIPT_OUT=$OUTPUT_DIR/runscript.log # File log for this script's stdout henceforth################## REPORT SCRIPT CONFIGURATION ###################echo "Command line:" | tee $SCRIPT_OUTecho "$0 $*" | tee -a $SCRIPT_OUTecho "================= Hardcoded directories ==================" | tee -a $SCRIPT_OUTecho "GEM5_DIR: $GEM5_DIR" | tee -a $SCRIPT_OUTecho "SPEC_DIR: $SPEC_DIR" | tee -a $SCRIPT_OUTecho "==================== Script inputs =======================" | tee -a $SCRIPT_OUTecho "BENCHMARK: $BENCHMARK" | tee -a $SCRIPT_OUTecho "OUTPUT_DIR: $OUTPUT_DIR" | tee -a $SCRIPT_OUTecho "==========================================================" | tee -a $SCRIPT_OUT###################################################################################### LAUNCH GEM5 SIMULATION ######################echo "" | tee -a $SCRIPT_OUTecho "" | tee -a $SCRIPT_OUTecho "--------- Here goes nothing! Starting gem5! ------------" | tee -a $SCRIPT_OUTecho "" | tee -a $SCRIPT_OUTecho "" | tee -a $SCRIPT_OUT# Actually launch gem5!##根据需要对此进行修改$GEM5_DIR/build/$ARCH/gem5.opt \\--debug-flags=MemoryAccess \\--outdir=$OUTPUT_DIR \\--debug-file=${BENCHMARK}.out \\$GEM5_DIR/configs/tutorial/spec06_config.py \\--benchmark=$BENCHMARK \\--benchmark_stdout=$OUTPUT_DIR/$BENCHMARK.out \\--benchmark_stderr=$OUTPUT_DIR/$BENCHMARK.err \\--num-cpus=1 \\--cpu-type=TimingSimpleCPU \\--cpu-clock=2.4GHz \\--caches \\--cacheline_size=64 \\--l1d_size=64kB --l1i_size=32kB --l1i_assoc=8 --l1d_assoc=8 \\--l2cache --l2_size=2MB --l2_assoc=8 \\--l3_size=32MB --l3_assoc=4 \\--ruby \\--mem-type=DDR4_2400_8x8 \\--mem-channels=2 \\--mem-ranks=8 \\--mem-size=4096MB -I 2000000 \\| tee -a $SCRIPT_OUT 可以使用如下命令单线程运行gem5仿真 cd $PATH_TO_YOUR_GEM5_INSTALL ./run_spec2006.sh <BENCHMARK_NAME> <FULL_PATH_TO_OUT_DIRECTORY> 多线程运行对上面的run_spec2006.sh进行修改最后的部分,得到多线程版本的脚本。 # Actually launch gem5!$GEM5_DIR/build/$ARCH/gem5.opt \\--debug-flags=MemoryAccess \\--outdir=$OUTPUT_DIR \\--debug-file=${BENCHMARK}.out \\$GEM5_DIR/configs/tutorial/spec06_config.py \\--benchmark=$BENCHMARK \\--benchmark_stdout=$OUTPUT_DIR/$BENCHMARK.out \\--benchmark_stderr=$OUTPUT_DIR/$BENCHMARK.err \\--num-cpus=4 \\--cpu-type=DerivO3CPU \\--cpu-clock=2.4GHz \\--caches \\--cacheline_size=64 \\--l1d_size=64kB --l1i_size=32kB --l1i_assoc=8 --l1d_assoc=8 \\--l2cache --l2_size=2MB --l2_assoc=8 \\--l3_size=32MB --l3_assoc=4 \\--ruby \\--mem-type=DDR4_2400_8x8 \\--mem-channels=2 \\--mem-ranks=8 \\--mem-size=4096MB -I 2000000 \\| tee -a $SCRIPT_OUT 可以使用如下命令单线程运行gem5仿真 cd $PATH_TO_YOUR_GEM5_INSTALL ./run_spec2006.sh <BENCHMARK_NAME1;BENCHMARK_NAME2;BENCHMARK_NAME3;BENCHMARK_NAME4> <FULL_PATH_TO_OUT_DIRECTORY> bench间使用”;”分隔。 参考: http://www.voidcn.com/article/p-ejrcfcvw-rb.htmlhttp://www.voidcn.com/article/p-nyjntyku-qo.htmlhttp://www.voidcn.com/article/p-waqmaqjn-rb.htmlhttp://www.voidcn.com/article/p-kmdrrpzd-qo.htmlhttp://www.voidcn.com/article/p-akuksawb-qo.htmlhttp://www.voidcn.com/article/p-ejrcfcvw-rb.htmlhttps://markgottscho.wordpress.com/2014/09/20/tutorial-easily-running-spec-cpu2006-benchmarks-in-the-gem5-simulator/","categories":[{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/categories/gem5/"}],"tags":[{"name":"GEM5","slug":"GEM5","permalink":"https://tomsworkspace.github.io/tags/GEM5/"},{"name":"CCPU2006","slug":"CCPU2006","permalink":"https://tomsworkspace.github.io/tags/CCPU2006/"}]},{"title":"值得推荐的C与C++框架和库","slug":"值得推荐的C与C++框架和库","date":"2020-08-05T03:25:35.000Z","updated":"2024-10-11T18:29:55.324Z","comments":true,"path":"2020/08/05/值得推荐的C与C++框架和库/","link":"","permalink":"https://tomsworkspace.github.io/2020/08/05/%E5%80%BC%E5%BE%97%E6%8E%A8%E8%8D%90%E7%9A%84C%E4%B8%8EC++%E6%A1%86%E6%9E%B6%E5%92%8C%E5%BA%93/","excerpt":"","text":"值得推荐的C/C++框架和库转载:EZLippi-值得推荐的C/C++框架和库 值得学习的C语言开源项目Libeventlibev是一个开源的事件驱动库,基于epoll,kqueue等OS提供的基础设施。其以高效出名,它可以将IO事件,定时器,和信号统一起来,统一放在事件处理这一套框架下处理。基于Reactor模式,效率较高,并且代码精简(4.15版本8000多行),是学习事件驱动编程的很好的资源。 下载链接:https://github.com/libevent/libevent MemcachedMemcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提供动态数据库驱动网站的速度。Memcached 基于一个存储键/值对的 hashmap。Memcached-1.4.7的代码量还是可以接受的,只有10K行左右。下载地址:http://memcached.org/ RedisRedis 是一个使用 C 语言写成的,开源的 key-value 数据库。Redis支持的操作和数据类型比Memcached要多,现在主要用于缓存,支持主从同步机制,Redis的学习可以参考<<Redis设计与实现>>一书。 下载地址:http://redis.io/ WebbenchWebbench是一个在linux下使用的非常简单的网站压测工具。它使用fork()模拟多个客户端同时访问我们设定的URL,测试网站在压力下工作的性能,最多可以模拟3万个并发连接去测试网站的负载能力。Webbench使用C语言编写, 代码实在太简洁,源码加起来不到600行。 下载链接:https://github.com/LippiOuYang/WebBenchl APR(Apache Portable Runtime)这是由 Apache 社区维护的 C 开源库,主要提供操作系统相关的功能(文件系统、进程、线程、用户、IPC)。此外还提供了一些网络相关的功能。 APR 原先是 Apache Web 服务器的一个组成部分,后来独立出来,成为一个单独的开源项目。主页:https://apr.apache.org NGINXNginx是由俄罗斯软件工程师Igor Sysoev开发的一个高性能的HTTP和反向代理服务器,具备IMAP/POP3和SMTP服务器功能。Nginx最大的特点是对高并发的支持和高效的负载均衡,在高并发的需求场景下,是Apache服务器不错的替代品。目前,包括新浪、腾讯等知名网站已经开始使用Nginx作为Web应用服务器。主页:http://nginx.org/en/download.html Tinyhttpdtinyhttpd是一个超轻量型Http Server,使用C语言开发,全部代码只有502行(包括注释),附带一个简单的Client,可以通过阅读这段代码理解一个 Http Server 的本质。 下载链接:https://github.com/LippiOuYang/Tinyhttpd cJSONcJSON是C语言中的一个JSON编解码器,非常轻量级,C文件只有500多行,速度也非常理想。cJSON也存在几个弱点,虽然功能不是非常强大,但cJSON的小身板和速度是最值得赞赏的。其代码被非常好地维护着,结构也简单易懂,可以作为一个非常好的C语言项目进行学习。 项目主页:http://sourceforge.net/projects/cjson/ CMockerycmockery是google发布的用于C单元测试的一个轻量级的框架。它很小巧,对其他开源包没有依赖,对被测试代码侵入性小。cmockery的源代码行数不到3K,你阅读一下will_return和mock的源代码就一目了然了。主要特点: 免费且开源,google提供技术支持;轻量级的框架,使测试更加快速简单;避免使用复杂的编译器特性,对老版本的编译器来讲,兼容性好;并不强制要求待测代码必须依赖C99标准,这一特性对许多嵌入式系统的开发很有用下载链接:http://code.google.com/p/cmockery/downloads/list LuaLua很棒,Lua是巴西人发明的,这些都令我不爽,但是还不至于脸红,最多眼红。让我脸红的是Lua的源代码,百分之一百的ANSI C,一点都不掺杂。在任何支持ANSI C编译器的平台上都可以轻松编译通过。我试过,真是一点废话都没有。Lua的代码数量足够小,5.1.4仅仅1.5W行,去掉空白行和注释估计能到1W行。下载地址:http://www.lua.org/ SQLiteSQLite是一个开源的嵌入式关系数据库,实现自包容、零配置、支持事务的SQL数据库引擎。 其特点是高度便携、使用方便、结构紧凑、高效、可靠。足够小,大致3万行C代码,250K。下载地址:http://www.sqlite.org/ 。 UNIX v6UNIX V6 的内核源代码包括设备驱动程序在内 约有1 万行,这个数量的源代码,初学者是能够充分理解的。有一种说法是一个人所能理解的代码量上限为1 万行,UNIX V6的内核源代码从数量上看正好在这个范围之内。看到这里,大家是不是也有“如果只有1万行的话没准儿我也能学会”的想法呢?另一方面,最近的操作系统,例如Linux 最新版的内核源代码据说超过了1000 万行。就算不是初学者,想完全理解全部代码基本上也是不可能的。 下载地址:http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6 NETBSDNetBSD是一个免费的,具有高度移植性的 UNIX-like 操作系统,是现行可移植平台最多的操作系统,可以在许多平台上执行,从 64bit alpha 服务器到手持设备和嵌入式设备。NetBSD计划的口号是:”Of course it runs NetBSD”。它设计简洁,代码规范,拥有众多先进特性,使得它在业界和学术界广受好评。由于简洁的设计和先进的特征,使得它在生产和研究方面,都有卓越的表现,而且它也有受使用者支持的完整的源代码。许多程序都可以很容易地通过NetBSD Packages Collection获得。 下载地址:http://www.netbsd.org/ 值得学习的C++开源项目LevelDbLevelDb是谷歌两位大神级别的工程师发起的开源项目,简而言之,LevelDb是能够处理十亿级别规模Key-Value型数据持久性存储的C++ 程序库。它是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDb不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。 其次,LevleDb在存储数据时,是根据记录的key值有序存储的,就是说相邻的key值在存储文件中是依次顺序存储的,而应用可以自定义key大小比较函数,LevleDb会按照用户定义的比较函数依序存储这些记录。 主页:https://github.com/google/leveldb Boost.Asio它是异步输入输出的核心。 名字本身就说明了一切:Asio 意即异步输入/输出。该库可以让 C++ 异步地处理数据,且平台独立。异步数据处理就是指,任务触发后不需要等待它们完成。相反,Boost.Asio 会在任务完成时触发一个应用。异步任务的主要优点在于,在等待任务完成时不需要阻塞应用程序,可以去执行其它任务。 异步任务的典型例子是网络应用。如果数据被发送出去了,比如发送至 Internet,通常需要知道数据是否发送成功。 如果没有一个象 Boost.Asio 这样的库,就必须对函数的返回值进行求值。但是,这样就要求待至所有数据发送完毕,并得到一个确认或是错误代码。而使用 Boost.Asio,这个过程被分为两个单独的步骤:第一步是作为一个异步任务开始数据传输。 一旦传输完成,不论成功或是错误,应用程序都会在第二步中得到关于相应的结果通知.主要的区别在于,应用程序无需阻塞至传输完成,而可以在这段时间里执行其它操作。 主页:http://www.boost.org/doc/libs/1_58_0/doc/html/boost_asio.html SGI STLSGI STL是STL代码的经典实现版本,虽然很多编译器不直接使用这个版本,但是很多却在此基础之上进行改进的。比如GNU C++的标准库就是在此基础之上改进的。这份代码还有一个好处是有注释,代码书写非常规范,只要花些时间读懂它并非难事。 主页:https://www.sgi.com/tech/stl/download.html Muduomuduo 是一个基于 Reactor 模式的现代 C++ 网络库,它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。 主页:https://github.com/chenshuo/muduo C++ 资源大全关于 C++ 框架、库和资源的一些汇总列表,内容包括:标准库、Web应用框架、人工智能、数据库、图片处理、机器学习、日志、代码分析等。 标准库C++标准库,包括了STL容器,算法和函数等。 C++ Standard Library :是一系列类和函数的集合,使用核心语言编写,也是C++ISO自身标准的一部分。 Standard Template Library :标准模板库 C POSIX library : POSIX系统的C标准库规范 ISO C++ Standards Committee :C++标准委员会 框架C++通用框架和库 Apache C++ Standard Library:是一系列算法,容器,迭代器和其他基本组件的集合 ASL :Adobe源代码库提供了同行的评审和可移植的C++源代码库。 Boost :大量通用C++库的集合。 BDE :来自于彭博资讯实验室的开发环境。 Cinder:提供专业品质创造性编码的开源开发社区。 Cxxomfort:轻量级的,只包含头文件的库,将C++ 11的一些新特性移植到C++03中。 Dlib:使用契约式编程和现代C++科技设计的通用的跨平台的C++库。 EASTL :EA-STL公共部分 ffead-cpp :企业应用程序开发框架 Folly:由Facebook开发和使用的开源C++库 JUCE :包罗万象的C++类库,用于开发跨平台软件 libPhenom:用于构建高性能和高度可扩展性系统的事件框架。 LibSourcey :用于实时的视频流和高性能网络应用程序的C++11 evented IO LibU : C语言写的多平台工具库 Loki :C++库的设计,包括常见的设计模式和习语的实现。 MiLi :只含头文件的小型C++库 openFrameworks :开发C++工具包,用于创意性编码。 Qt :跨平台的应用程序和用户界面框架 Reason :跨平台的框架,使开发者能够更容易地使用Java,.Net和Python,同时也满足了他们对C++性能和优势的需求。 ROOT :具备所有功能的一系列面向对象的框架,能够非常高效地处理和分析大量的数据,为欧洲原子能研究机构所用。 STLport:是STL具有代表性的版本 STXXL:用于额外的大型数据集的标准模板库。 Ultimate++ :C++跨平台快速应用程序开发框架 Windows Template Library:用于开发Windows应用程序和UI组件的C++库 Yomm11 :C++11的开放multi-methods. 人工智能 btsk :游戏行为树启动器工具 Evolving Objects:基于模板的,ANSI C++演化计算库,能够帮助你非常快速地编写出自己的随机优化算法。 Neu:C++11框架,编程语言集,用于创建人工智能应用程序的多用途软件系统。 异步事件循环 Boost.Asio:用于网络和底层I/O编程的跨平台的C++库。 libev :功能齐全,高性能的时间循环,轻微地仿效libevent,但是不再像libevent一样有局限性,也修复了它的一些bug。 libevent :事件通知库 libuv :跨平台异步I/O。 音频音频,声音,音乐,数字化音乐库 FMOD :易于使用的跨平台的音频引擎和音频内容的游戏创作工具。 Maximilian :C++音频和音乐数字信号处理库 OpenAL :开源音频库—跨平台的音频API Opus:一个完全开放的,免版税的,高度通用的音频编解码器 Speex:免费编解码器,为Opus所废弃 Tonic: C++易用和高效的音频合成 Vorbis: Ogg Vorbis是一种完全开放的,非专有的,免版税的通用压缩音频格式。 生态学生物信息,基因组学和生物技术 libsequence:用于表示和分析群体遗传学数据的C++库。 SeqAn:专注于生物数据序列分析的算法和数据结构。 Vcflib :用于解析和处理VCF文件的C++库 Wham:直接把联想测试应用到BAM文件的基因结构变异。 压缩压缩和归档库 bzip2:一个完全免费,免费专利和高质量的数据压缩 doboz:能够快速解压缩的压缩库 PhysicsFS:对各种归档提供抽象访问的库,主要用于视频游戏,设计灵感部分来自于Quake3的文件子系统。 KArchive:用于创建,读写和操作文件档案(例如zip和 tar)的库,它通过QIODevice的一系列子类,使用gzip格式,提供了透明的压缩和解压缩的数据。 LZ4 :非常快速的压缩算法 LZHAM :无损压缩数据库,压缩比率跟LZMA接近,但是解压缩速度却要快得多。 LZMA :7z格式默认和通用的压缩方法。 LZMAT :及其快速的实时无损数据压缩库 miniz:单一的C源文件,紧缩/膨胀压缩库,使用zlib兼容API,ZIP归档读写,PNG写方式。 Minizip:Zlib最新bug修复,支持PKWARE磁盘跨越,AES加密和IO缓冲。 Snappy :快速压缩和解压缩 ZLib :非常紧凑的数据流压缩库 ZZIPlib:提供ZIP归档的读权限。 并发并发执行和多线程 Boost.Compute :用于OpenCL的C++GPU计算库 Bolt :针对GPU进行优化的C++模板库 C++React :用于C++11的反应性编程库 Intel TBB :Intel线程构件块 Libclsph:基于OpenCL的GPU加速SPH流体仿真库 OpenCL :并行编程的异构系统的开放标准 OpenMP:OpenMP API Thrust :类似于C++标准模板库的并行算法库 HPX :用于任何规模的并行和分布式应用程序的通用C++运行时系统 VexCL :用于OpenCL/CUDA 的C++向量表达式模板库。 容器 C++ B-tree :基于B树数据结构,实现命令内存容器的模板库 Hashmaps: C++中开放寻址哈希表算法的实现 密码学 Bcrypt :一个跨平台的文件加密工具,加密文件可以移植到所有可支持的操作系统和处理器中。 BeeCrypt: Botan: C++加密库 Crypto++:一个有关加密方案的免费的C++库 GnuPG: OpenPGP标准的完整实现 GnuTLS :实现了SSL,TLS和DTLS协议的安全通信库 Libgcrypt libmcrypt LibreSSL:免费的SSL/TLS协议,属于2014 OpenSSL的一个分支 LibTomCrypt:一个非常全面的,模块化的,可移植的加密工具 libsodium:基于NaCI的加密库,固执己见,容易使用 Nettle 底层的加密库 OpenSSL : 一个强大的,商用的,功能齐全的,开放源代码的加密库。 Tiny AES128 in C :用C实现的一个小巧,可移植的实现了AES128ESB的加密算法 数据库数据库,SQL服务器,ODBC驱动程序和工具 hiberlite :用于Sqlite3的C++对象关系映射 Hiredis: 用于Redis数据库的很简单的C客户端库 LevelDB: 快速键值存储库 LMDB:符合数据库四大基本元素的嵌入键值存储 MySQL++:封装了MySql的C API的C++ 包装器 RocksDB:来自Facebook的嵌入键值的快速存储 SQLite:一个完全嵌入式的,功能齐全的关系数据库,只有几百KB,可以正确包含到你的项目中。 测试调试库, 内存和资源泄露检测,单元测试 Boost.Test:Boost测试库 Catch:一个很时尚的,C++原生的框架,只包含头文件,用于单元测试,测试驱动开发和行为驱动开发。 CppUnit:由JUnit移植过来的C++测试框架 CTest:CMake测试驱动程序 googletest:谷歌C++测试框架 ig-debugheap:用于跟踪内存错误的多平台调试堆 libtap:用C语言编写测试 MemTrack —用于C++跟踪内存分配 microprofile- 跨平台的网络试图分析器 minUnit :使用C写的迷你单元测试框架,只使用了两个宏 Remotery:用于web视图的单一C文件分析器 UnitTest++:轻量级的C++单元测试框架 游戏引擎 Unity(Unity3D):跨平台,Unity提供一整套软件解决方案,可用于创作、运营和变现实时互动的2D和3D内容。 Cocos2d-x :一个跨平台框架,用于构建2D游戏,互动图书,演示和其他图形应用程序。 Grit :社区项目,用于构建一个免费的游戏引擎,实现开放的世界3D游戏。 Irrlicht :C++语言编写的开源高性能的实时#D引擎 Polycode:C++实现的用于创建游戏的开源框架(与Lua绑定)。 图形用户界面 CEGUI : 很灵活的跨平台GUI库 FLTK :快速,轻量级的跨平台的C++GUI工具包。 GTK+: 用于创建图形用户界面的跨平台工具包 gtkmm :用于受欢迎的GUI库GTK+的官方C++接口。 imgui:拥有最小依赖关系的立即模式图形用户界面 libRocket :libRocket 是一个C++ HTML/CSS 游戏接口中间件 MyGUI :快速,灵活,简单的GUI Ncurses:终端用户界面 QCustomPlot :没有更多依赖关系的Qt绘图控件 Qwt :用户与技术应用的Qt 控件 QwtPlot3D :功能丰富的基于Qt/OpenGL的C++编程库,本质上提供了一群3D控件 OtterUI :OtterUI 是用于嵌入式系统和互动娱乐软件的用户界面开发解决方案 PDCurses 包含源代码和预编译库的公共图形函数库 wxWidgets C++库,允许开发人员使用一个代码库可以为widows, Mac OS X,Linux和其他平台创建应用程序 图形 bgfx:跨平台的渲染库 Cairo:支持多种输出设备的2D图形库 Horde3D 一个小型的3D渲染和动画引擎 magnum C++11和OpenGL 2D/3D 图形引擎 Ogre 3D 用C++编写的一个面向场景,实时,灵活的3D渲染引擎(并非游戏引擎) OpenSceneGraph 具有高性能的开源3D图形工具包 Panda3D 用于3D渲染和游戏开发的框架,用Python和C++编写。 Skia 用于绘制文字,图形和图像的完整的2D图形库 urho3d 跨平台的渲染和游戏引擎。 图像处理 Boost.GIL:通用图像库 CImg :用于图像处理的小型开源C++工具包 CxImage :用于加载,保存,显示和转换的图像处理和转换库,可以处理的图片格式包括 BMP, JPEG, GIF, PNG, TIFF, MNG, ICO, PCX, TGA, WMF, WBMP, JBG, J2K。 FreeImage :开源库,支持现在多媒体应用所需的通用图片格式和其他格式。 GDCM:Grassroots DICOM 库 ITK:跨平台的开源图像分析系统 Magick++:ImageMagick程序的C++接口 MagickWnd:ImageMagick程序的C++接口 OpenCV : 开源计算机视觉类库 tesseract-ocr:OCR引擎 VIGRA :用于图像分析通用C++计算机视觉库 VTK :用于3D计算机图形学,图像处理和可视化的开源免费软件系统。 国际化 gettext :GNU `gettext’ IBM ICU:提供Unicode 和全球化支持的C、C++ 和Java库 libiconv :用于不同字符编码之间的编码转换库 Jason frozen : C/C++的Jason解析生成器 Jansson :进行编解码和处理Jason数据的C语言库 jbson :C++14中构建和迭代BSON data,和Json 文档的库 JeayeSON:非常健全的C++ JSON库,只包含头文件 JSON++ : C++ JSON 解析器 json-parser:用可移植的ANSI C编写的JSON解析器,占用内存非常少 json11 :一个迷你的C++11 JSON库 jute :非常简单的C++ JSON解析器 ibjson:C语言中的JSON解析和打印库,很容易和任何模型集成。 libjson:轻量级的JSON库 PicoJSON:C++中JSON解析序列化,只包含头文件 qt-json :用于JSON数据和 QVariant层次间的相互解析的简单类 QJson:将JSON数据映射到QVariant对象的基于Qt的库 RapidJSON: 用于C++的快速JSON 解析生成器,包含SAX和DOM两种风格的API YAJL :C语言中快速流JSON解析库 日志 Boost.Log :设计非常模块化,并且具有扩展性 easyloggingpp:C++日志库,只包含单一的头文件。 Log4cpp :一系列C++类库,灵活添加日志到文件,系统日志,IDSA和其他地方。 templog:轻量级C++库,可以添加日志到你的C++应用程序中 机器学习 Caffe :快速的神经网络框架 CCV :以C语言为核心的现代计算机视觉库 mlpack :可扩展的C++机器学习库 OpenCV:开源计算机视觉库 Recommender:使用协同过滤进行产品推荐/建议的C语言库。 SHOGUN:Shogun 机器学习工具 sofia-ml :用于机器学习的快速增量算法套件 数学 Armadillo :高质量的C++线性代数库,速度和易用性做到了很好的平衡。语法和MatlAB很相似 blaze:高性能的C++数学库,用于密集和稀疏算法。 ceres-solver :来自谷歌的C++库,用于建模和解决大型复杂非线性最小平方问题。 CGal: 高效,可靠的集合算法集合 cml :用于游戏和图形的免费C++数学库 Eigen :高级C++模板头文件库,包括线性代数,矩阵,向量操作,数值解决和其他相关的算法。 GMTL:数学图形模板库是一组广泛实现基本图形的工具。 GMP:用于个高精度计算的C/C++库,处理有符号整数,有理数和浮点数。 多媒体 GStreamer :构建媒体处理组件图形的库 LIVE555 Streaming Media :使用开放标准协议(RTP/RTCP, RTSP, SIP) 的多媒体流库 libVLC :libVLC (VLC SDK)媒体框架 QtAv:基于Qt和FFmpeg的多媒体播放框架,能够帮助你轻而易举地编写出一个播放器 SDL :简单直控媒体层 SFML :快速,简单的多媒体库 网络 ACE:C++面向对象网络变成工具包 Boost.Asio:用于网络和底层I/O编程的跨平台的C++库 Casablanca:C++ REST SDK cpp-netlib:高级网络编程的开源库集合 Dyad.c:C语言的异步网络 libcurl :多协议文件传输库 Mongoose:非常轻量级的网络服务器 Muduo :用于Linux多线程服务器的C++非阻塞网络库 net_skeleton :C/C++的TCP 客户端/服务器库 nope.c :基于C语言的超轻型软件平台,用于可扩展的服务器端和网络应用。 对于C编程人员,可以考虑node.js Onion :C语言HTTP服务器库,其设计为轻量级,易使用。 POCO:用于构建网络和基于互联网应用程序的C++类库,可以运行在桌面,服务器,移动和嵌入式系统。 RakNet:为游戏开发人员提供的跨平台的开源C++网络引擎。 Tuf o :用于Qt之上的C++构建的异步Web框架。 WebSocket++ :基于C++/Boost Aiso的websocket 客户端/服务器库 ZeroMQ :高速,模块化的异步通信库 物理学动力学仿真引擎 Box2D:2D的游戏物理引擎。 Bullet :3D的游戏物理引擎。 Chipmunk :快速,轻量级的2D游戏物理库 LiquidFun:2D的游戏物理引擎 ODE :开放动力学引擎-开源,高性能库,模拟刚体动力学。 ofxBox2d:Box2D开源框架包装器。 Simbody :高性能C++多体动力学/物理库,模拟关节生物力学和机械系统,像车辆,机器人和人体骨骼。 机器人学 MOOS-IvP :一组开源C++模块,提供机器人平台的自主权,尤其是自主的海洋车辆。 MRPT:移动机器人编程工具包 PCL :点云库是一个独立的,大规模的开放项目,用于2D/3D图像和点云处理。 Robotics Library (RL): 一个独立的C++库,包括机器人动力学,运动规划和控制。 RobWork:一组C++库的集合,用于机器人系统的仿真和控制。 ROS :机器人操作系统,提供了一些库和工具帮助软件开发人员创建机器人应用程序。 科学计算 FFTW :用一维或者多维计算DFT的C语言库。 GSL:GNU科学库。 脚本 ChaiScript :用于C++的易于使用的嵌入式脚本语言。 Lua :用于配置文件和基本应用程序脚本的小型快速脚本引擎。 luacxx:用于创建Lua绑定的C++ 11 API SWIG :一个可以让你的C++代码链接到JavaScript,Perl,PHP,Python,Tcl和Ruby的包装器/接口生成器 V7:嵌入式的JavaScript 引擎。 V8 :谷歌的快速JavaScript引擎,可以被嵌入到任何C++应用程序中。 序列化 Cap’n Proto :快速数据交换格式和RPC系统。 cereal :C++11 序列化库 FlatBuffers :内存高效的序列化库 MessagePack :C/C++的高效二进制序列化库,例如 JSON protobuf :协议缓冲,谷歌的数据交换格式。 protobuf-c :C语言的协议缓冲实现 SimpleBinaryEncoding:用于低延迟应用程序的对二进制格式的应用程序信息的编码和解码。 Thrift :高效的跨语言IPC/RPC,用于C++,Java,Python,PHP,C#和其它多种语言中,最初由Twitter开发。 视频 libvpx :VP8/VP9编码解码SDK FFmpeg :一个完整的,跨平台的解决方案,用于记录,转换视频和音频流。 libde265 :开放的h.265视频编解码器的实现。 OpenH264:开源H.364 编解码器。 Theora :免费开源的视频压缩格式。 虚拟机 CarpVM:C中有趣的VM,让我们一起来看看这个。 MicroPython :旨在实现单片机上Python3.x的实现 TinyVM:用纯粹的ANSI C编写的小型,快速,轻量级的虚拟机。 Web应用框架 Civetweb :提供易于使用,强大的,C/C++嵌入式Web服务器,带有可选的CGI,SSL和Lua支持。 CppCMS :免费高性能的Web开发框架(不是 CMS). Crow :一个C++微型web框架(灵感来自于Python Flask) Kore :使用C语言开发的用于web应用程序的超快速和灵活的web服务器/框架。 libOnion:轻量级的库,帮助你使用C编程语言创建web服务器。 QDjango:使用C++编写的,基于Qt库的web框架,试图效仿Django API,因此得此名。 Wt :开发Web应用的C++库。 XMLXML就是个垃圾,xml的解析很烦人,对于计算机它也是个灾难。这种糟糕的东西完全没有存在的理由了。-Linus Torvalds Expat :用C语言编写的xml解析库 Libxml2 :Gnome的xml C解析器和工具包 libxml++ :C++的xml解析器 PugiXML :用于C++的,支持XPath的轻量级,简单快速的XML解析器。 RapidXml :试图创建最快速的XML解析器,同时保持易用性,可移植性和合理的W3C兼容性。 TinyXML :简单小型的C++XML解析器,可以很容易地集成到其它项目中。 TinyXML2:简单快速的C++CML解析器,可以很容易集成到其它项目中。 TinyXML++:TinyXML的一个全新的接口,使用了C++的许多许多优势,模板,异常和更好的异常处理。 Xerces-C++ :用可移植的C++的子集编写的XML验证解析器。 多项混杂一些有用的库或者工具,但是不适合上面的分类,或者还没有分类。 C++ Format :C++的小型,安全和快速格式化库 casacore :从aips++ 派生的一系列C++核心库 cxx-prettyprint:用于C++容器的打印库 DynaPDF :易于使用的PDF生成库 gcc-poison :帮助开发人员禁止应用程序中的不安全的C/C++函数的简单的头文件。 googlemock:编写和使用C++模拟类的库 HTTP Parser :C的http请求/响应解析器 libcpuid :用于x86 CPU检测盒特征提取的小型C库 libevil :许可证管理器 libusb:允许移动访问USB设备的通用USB库 PCRE:正则表达式C库,灵感来自于Perl中正则表达式的功能。 Remote Call Framework :C++的进程间通信框架。 Scintilla :开源的代码编辑控件 Serial Communication Library :C++语言编写的跨平台,串口库。 SDS:C的简单动态字符串库 SLDR :超轻的DNS解析器 SLRE: 超轻的正则表达式库 Stage :移动机器人模拟器 VarTypes:C++/Qt4功能丰富,面向对象的管理变量的框架。 ZBar:‘条形码扫描器’库,可以扫描照片,图片和视频流中的条形码,并返回结果。 CppVerbalExpressions :易于使用的C++正则表达式 QtVerbalExpressions:基于C++ VerbalExpressions 库的Qt库 PHP-CPP:使用C++来构建PHP扩展的库 Better String :C的另一个字符串库,功能更丰富,但是没有缓冲溢出问题,还包含了一个C++包装器。 软件用于创建开发环境的软件 编译器C/C++编译器列表 Clang :由苹果公司开发的 GCC:GNU编译器集合 Intel C++ Compiler :由英特尔公司开发 LLVM :模块化和可重用编译器和工具链技术的集合 Microsoft Visual C++ :MSVC,由微软公司开发 Open WatCom :Watcom,C,C++和Fortran交叉编译器和工具 TCC :轻量级的C语言编译器 在线编译器在线C/C++编译器列表 codepad :在线编译器/解释器,一个简单的协作工具 CodeTwist:一个简单的在线编译器/解释器,你可以粘贴的C,C++或者Java代码,在线执行并查看结果 coliru :在线编译器/shell, 支持各种C++编译器 Compiler Explorer:交互式编译器,可以进行汇编输出 CompileOnline:Linux上在线编译和执行C++程序 Ideone :一个在线编译器和调试工具,允许你在线编译源代码并执行,支持60多种编程语言。 调试器C/C++调试器列表 Comparison of debuggers :来自维基百科的调试器列表 GDB :GNU调试器 Valgrind:内存调试,内存泄露检测,性能分析工具。 集成开发环境(IDE)C/C++集成开发环境列表 AppCode :构建与JetBrains’ IntelliJ IDEA 平台上的用于Objective-C,C,C++,Java和Java开发的集成开发环境 CLion:来自JetBrains的跨平台的C/C++的集成开发环境 Code::Blocks :免费C,C++和Fortran的集成开发环境 CodeLite :另一个跨平台的免费的C/C++集成开发环境 Dev-C++:可移植的C/C++/C++11集成开发环境 Eclipse CDT:基于Eclipse平台的功能齐全的C和C++集成开发环境 Geany :轻量级的快速,跨平台的集成开发环境。 IBM VisualAge :来自IBM的家庭计算机集成开发环境。 Irony-mode:由libclang驱动的用于Emacs的C/C++微模式 KDevelop:免费开源集成开发环境 Microsoft Visual Studio :来自微软的集成开发环境 NetBeans :主要用于Java开发的的集成开发环境,也支持其他语言,尤其是PHP,C/C++和HTML5。 Qt Creator:跨平台的C++,Javascript和QML集成开发环境,也是Qt SDK的一部分。 rtags:C/C++的客户端服务器索引,用于 跟基于clang的emacs的集成 Xcode :由苹果公司开发 YouCompleteMe:一个用于Vim的根据你敲的代码快速模糊搜索并进行代码补全的引擎。 构建系统 Bear :用于为clang工具生成编译数据库的工具 Biicode:基于文件的简单依赖管理器。 CMake :跨平台的免费开源软件用于管理软件使用独立编译的方法进行构建的过程。 CPM:基于CMake和Git的C++包管理器 FASTBuild:高性能,开源的构建系统,支持高度可扩展性的编译,缓冲和网络分布。 Ninja :专注于速度的小型构建系统 Scons :使用Python scipt 配置的软件构建工具 tundra :高性能的代码构建系统,甚至对于非常大型的软件项目,也能提供最好的增量构建次数。 tup:基于文件的构建系统,用于后台监控变化的文件。 静态代码分析提高质量,减少瑕疵的代码分析工具列表 Cppcheck :静态C/C++代码分析工具 include-what-you-use :使用clang进行代码分析的工具,可以#include在C和C++文件中。 OCLint :用于C,C++和Objective-C的静态源代码分析工具,用于提高质量,减少瑕疵。 Clang Static Analyzer:查找C,C++和Objective-C程序bug的源代码分析工具 List of tools for static code analysis :来自维基百科的静态代码分析工具列表","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"C","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"}]},{"title":"C语言标准","slug":"C语言标准","date":"2020-02-24T14:25:27.000Z","updated":"2024-10-11T18:29:55.288Z","comments":true,"path":"2020/02/24/C语言标准/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E8%AF%AD%E8%A8%80%E6%A0%87%E5%87%86/","excerpt":"","text":"C语言标准发展C语言的起源 1972年,贝尔实验室的丹尼斯.里奇(Dennis Ritch)和肯.汤普逊(Ken Thompson)在开发UNIX操作系统时设计了C语言。然而C语言并不完全是里奇突发奇想而来,他是在B语言(汤普逊发明)的基础上进行设计。C语言设计的初衷是将其作为程序员使用的一种编程工具,因此,其主要目标是成为有用的语言。 C语言标准 目前,有许多C实现可用。在理想情况下,编写C程序时,假设该程序中未使用机器特定的编程技术,那么它的运行情况在任何实现中都应该相同。要在实践中做到这一点,不同的实现要遵循一个标准。  C语言发展之初,并没有所谓的C标准。1987年,布莱恩.柯林汉(Brian Kernighan)和丹尼斯.里奇(Dennis Ritch)和著的The Programming Language(C语言程序设计)第一版是公认的C标准,通常称之为K&R C或经典C。特别是,该书中的附录中的"C语言参考手册"已成为C的指导标准。虽然这本书的附录定义了C语言,但是并没有定义C库。与大多数语言不同的是,C语言比其他语言更依赖库,因此需要一个标准库。实际上,由于缺乏官方标准,UNIX实现提供的库已经成为了标准库。 第一个ANSI/ISO C标准 随着C的不断发展,越来越广泛地运用于更多的系统中,C社区意识到需要一个更全面,更新颖,更严格的标准。鉴于此,美国国家标准协会(ANSI)于1983年组建了一个委员会(X3J11),开发了一套新标准,并于1989年正式公布。该标准(ANSI C)定义了C语言和C标准库。国际标准化组织于1990年采用了这套C标准(ISO C)。ISO C和ANSI C是完全相同过的标准。ANSI/ISO标准的最终版本通常叫做C89(ANSI 于1989年批准该标准)或C90(ISO于1990年批准该标准)。另外,由于ANSI先公布C标准,因此业界人士通常使用ANSI C。 C99标准 1994年,ANSI/ISO联合委员会(C9X委员会)开始修订C标准,最终发布了C99标准。该委员会遵循了最初C90的原则,包括保持语言的精炼简单。委员会的用意不是在C语言中添加新特性,而是为了达到新的目标。第一个目标是,支持国际化编程。例如提供多种方法处理国际字符集。第二个目标是,“调整现有实现致力于解决明显的缺陷”。因此,在遇到需要将C移至64位处理器时,委员会根据现实生活中处理问题的经验来添加标准。第三个目标是,为适应科学和工程项目中的关键数值计算,提高C的适应性,让C比FORTRAN更有竞争力。  C99的修订保留了C语言的精髓,C任是一门简洁高效的语言。虽然该标准已经发布了很长时间,但并非所有的编译器都完全实现C99的所有改动。 C11标准 标准委员会在2007年承诺C标准的下一个版本是C1X,2011年终于发布了C11标准。此次,委员会提出了一些新的指导原则。由于供应商并未像C90那样很好地接和支持C99,这使得C99中的一些特性成为C11的可选项。因为委员会认为,不应要求服务小型机市场的供应商支持其目标环境中用不到的特性。另外需要强调的是,修订标准的原因不是因为原本的标准不能用,而是需要跟进新的技术。例如,新标准添加了可选项支持当前使用多处理器的计算机。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 标准","slug":"C-标准","permalink":"https://tomsworkspace.github.io/tags/C-%E6%A0%87%E5%87%86/"}]},{"title":"C与C++的区别","slug":"C与C++的区别","date":"2020-02-24T12:32:16.000Z","updated":"2024-10-11T18:29:55.283Z","comments":true,"path":"2020/02/24/C与C++的区别/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E4%B8%8EC++%E7%9A%84%E5%8C%BA%E5%88%AB/","excerpt":"","text":"C和C++的区别 参考:C Primer plus 6th  在很大程度上,C++是C的超集,这意味着一个有效的C程序也是一个有效的C++程序。C和C++的主要区别是,C++支持许多附加特性。但是,C++中许多规则与C稍有不同。这些不同使得C程序作为C++程序编译时可能以不同的方式运行或者根本不能运行。 C99标准的发布使得问题更加复杂,因为有些情况使得C更接近C++。例如,C99标准允许在代码中的任意处进行声明,而且可以识别//注释指示符。在其他方面,C99使得其与C++的差异变大。例如,新增了变长数组和关键字restrict。C11缩小了与C++的差异。例如,引进了char16_t类型,新增了关键字_Alignas,新增了alignas宏与C++的关键字匹配。C11仍处于起步阶段,许多编译器开发商甚至都没有完全支持C99。要了解它们之间的区别,需要了解C90,C99,C11之间的区别,还要了解C++11与这些标准之间的区别,以及每个标准与C标准之间的区别。下面主要讨论C99,C11和C++之间的区别。由于C++也在发展,因此,C与C++之间的异同也在不断发展变化。 一. 函数原型  在C++中,函数原型是必不可少的,但是在C中是可选的。  C++允许用户声明多个同名函数,只要参数列表不同即可,这是C++的重载特性。但是在C中这是不可以的。 二. Char常量  C把char视为int类型,而C++将其视为char类型。  在C中,常量’A’被存储在int大小的内存块中,字符编码被存储为一个int类型的值。字符变量在C中占一个内存字节。但是在C++中,字符常量和变量都占用一个字节。 三. Const限定符  在C中,全局的const具有外部链接,但是在C++中,具有内部链接。  在C++中,可以使用const值来初始化其他const变量,在C中不能这样做。 四. 结构和联合  声明一个带标记的结构或联合后,就可以在C++中使用这个标记作为类型名。C中不可以。  在C和C++中,都可以嵌套声明结构,在C中可以认识使用这些结构,但在C++中使用内部的结构需要使用一个特殊的符号“::”。 五. 枚举  C++使用枚举比C更加严格,只能把枚举常量赋值给枚举变量,要把int类型赋值给枚举变量必须要显式强制类型转换,而且也不能递增枚举变量。  C++中可以把声明的枚举变量作为类型声明其他枚举变量。C中不可以这样做。 六. 指向void的指针  C和C++都可以把任意类型的指针赋值给指向void的指针。但是C++只由使用显式强制类型转换才能把指向void的指针赋值给其他类型的指针。  C++可以把派生类的对象地址赋值给基类指针,但是在C中没有相关的特性。 七. 布尔类型  在C++中,布尔类型是bool,true和false都是关键字。在C中布尔类型是_Bool,但是要包含stdbool.h才可以使用bool,true和false。 八. 可选拼写  在C++中,可以用or代替||,还有一些其他拼写,都是关键字。C中这些可选拼写都被定义为宏,要包含iso646.h才能用。 九. 宽字符支持  在C++中,wchar_t是内置类型,而且是关键字。在C中,wchar_t被定义在多个头文件中(stddef.h stdlib.h wchar.h wctype.h)。与此类似,char16_t和char32_t都是C++11的关键字,在C11中被定义在uchar.h中。 十. 复数类型  C++在complex头文件中提供一个复数类来支持复数类型。C有内置的复数类型,并通过complex.h来支持。这两种方法区别很大,不兼容。 十一. 内联函数  C99支持了C++的内联函数特性,但是,C99的实现更加灵活。在C++中,内联函数默认是内部链接,在C++中,如果允许一个内联函数多次出现在多个文件中,该函数定义必须相同,而且要使用相同的语言记号。C中没有这个要求。  C允许混合使用内联定义和外部定义,C++不允许。 C++11中没有的C99/C11特性  指定初始化器  复合初始化器(Compound initializer)  受限指针(Restrict pointer)  变长数组  伸缩型数组成员  带可变数量参数的宏","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C C++ 区别","slug":"C-C-区别","permalink":"https://tomsworkspace.github.io/tags/C-C-%E5%8C%BA%E5%88%AB/"}]},{"title":"C11关键字_Static_assert","slug":"C11关键字_Static_assert","date":"2020-02-24T12:31:58.000Z","updated":"2024-10-11T18:29:55.269Z","comments":true,"path":"2020/02/24/C11关键字_Static_assert/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C11%E5%85%B3%E9%94%AE%E5%AD%97_Static_assert/","excerpt":"","text":"C11新增关键字_Static_assert C11标准新增了一个关键字_Static_assert,下面来介绍一下它的相关用法和注意事项。 用法 和C11以前的断言assert()表达式有些类似。assert()表达式是在运行时检查。而C11新增的_Static_assert声明可以在编译时检查assert()表达式。因此,assert()可以导致正在运行的函数中止,而_Static_assert()可以在导致程序无法通过编译。_Static_assert()接受两个参数。第一个参数是一个整型常量表达式,第二个参数是一个字符串。如果第一个表达式求值为0(或_False),编译会显示字符串,而且不编译该程序。 例程下面是一个使用_Static_assert的例程: #include <stdio.h>#include <limits.h>_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");int main(void){ puts("char is 16 bits."); return 0;}  编译结果 statasrt.c:4:1:Error:static assertion failed: "16-bit char falsely assumed"_Static_assert(CHAR_BIT == 16, "16-bit char falsely assumed");^ ~~~~~~~~~~~~~~~~~~~~~~~1 error generated. 注意事项 性能方面:assert()是运行期断言,它用来发现运行期间的错误,不能提前到编译期发现错误,也不具有强制性,也谈不上改善编译信息的可读性。既然是运行期检查,对性能肯定是有影响的,所以经常在发行版本中,assert都会被关掉。由于static_assert是编译期间断言,不生成目标代码,因此static_assert不会造成任何运行期性能损失。 使用范围:static_assert可以用在全局作用域中,命名空间中,类作用域中,函数作用域中,几乎可以不受限制的使用。 常量表达式:static_assert的断言表达式的结果必须是在编译时期可以计算的表达式,即必须是常量表达式。如果使用变量会导致错误。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C11 关键字 _Static_assert","slug":"C11-关键字-Static-assert","permalink":"https://tomsworkspace.github.io/tags/C11-%E5%85%B3%E9%94%AE%E5%AD%97-Static-assert/"}]},{"title":"C语言指针与多维数组","slug":"C语言指针与多维数组","date":"2020-02-24T12:31:17.000Z","updated":"2024-10-11T18:29:55.286Z","comments":true,"path":"2020/02/24/C语言指针与多维数组/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E8%AF%AD%E8%A8%80%E6%8C%87%E9%92%88%E4%B8%8E%E5%A4%9A%E7%BB%B4%E6%95%B0%E7%BB%84/","excerpt":"","text":"指针与多维数组的关系 最近在复习C语言,学到C指针的时候,发现指针与多维数组间存在着一些很有意思的问题,这也是在我们使用指针时容易出现问题的地方。先从一个例子开始: int zippo[4][2];  这个例子声明了一个4x2共8个int类型的二维数组。数组名zippo是该数组首元素的地址,所以zippo的值和&zippo[0]的值相同。而zippo[0]本身是一个内含两个整数的数组,所以zippo[0]的值和它首元素的地址(即&zippo[0][0]的值)相同。简而言之,zippo[0]是占用一个int大小对象的地址,而zippo是一个占用两个int大小对象的地址。由于这个整数和内含两个整数的数组都开始于同一个地址,所以zippo和&zippo[0]的值相同。  给指针或者是地址加1,其值会增加相同类型大小的数值。在这方面,zippo和zippo[0]不同,因为zippo指向的对象占两个int大小,而zippo[0]指向的对象占用一个int大小。因此,zippo+1和zippo[0]+1的值不同。  解引用一个指针(在指针前使用*运算符)或在数组名后使用带下标的[]运算符,得到引用对象代表的值。因为zippo[0]是该数组首元素(zippo[0][0])的地址,所以*(zippo[0])表示存储在zippo[0][0]上的值。与此类似,*zippo代表数组首元素(zippo[0])的值,但是zippo[0]本身是一个int类型值的地址。该值的地址是&zippo[0][0],所以*zippo就是&zippo[0][0]。**zippo和*&zippo[0][0]等价,相当于zippo[0][0],即一个int类型的值。总之,zippo是地址的地址,必须解引用两次才能获得原始值。地址的地址或指针的指针叫做双重间接(double indirection)。  由于指针可以说是一个地址,在多维数组中,可以使用指针表示法和数组表示法表达同一个意思。一些常见的等价用法如下: 数组表示法 指针表示法 描述 zippo 无 二维数组首元素的地址,每个元素都是内含两个int类型元素的数组 zippo+2 无 二维数组第三个元素的地址,指向一维数组 &zippo[0][0] *zippo 二维数组第一个元素(一维数组)的首元素(int)的地址 zippo[0] 无 二维数组首元素的地址,是内含两个int类型元素的一维数组 &zippo[0] 无 二维数组首元素的地址,每个元素都是内含两个int类型元素的数组 *zippo[0] 无 二维数组首元素(一维数组)的的首元素的值,即zippo[0][0] &zippo[2][0] *(zippo+2) 二维数组第三个元素(一维数组)的首元素(int)的地址 &zippo[2][1] *(zippo+2)+1 二维数组第三个元素(一维数组)的第二个元素(int)的地址 zippo[2][1] *(*(zippo+2)+1) 二维数组第三个元素(一维数组)的第二个元素(int)的值 一些复杂的声明 了解了指针与多维数组的关系,下面再来了解一些复杂的数组声明熟悉一下。 声明 描述 int board[8][8]; 声明一个8x8的二维int数组 int **ptr; 声明一个指向指针的指针,被指向的指针指向int int *risks[10]; 声明一个内含10个元素的数组,每个元素都是一个指向int的指针 int (*risks)[10]; 声明一个指向数组的指针,该数组内含10个int类型的值 int *oof[3][4]; 声明一个3x4的二维数组,每个元素都是指向int的指针 int (*off)[3][4]; 声明一个指向3x4的二维数组的指针,数组内含int类型值 int (* uof[3])[4]; 声明一个内含3个指针的数组,其中每个指针都指向一个内含4个int类型元素的数组  要看懂如上的声明,关键要理解*,()和[]的优先级。有如下的几条规则。  1.数组名后面的[]和函数名后的()具有相同的优先级。他们比*(解引用运算符)的优先级高。因此下面的声明是一个指针数组,而不是一个指向数组的指针: int *risks[10];  2.[]和()的优先级相同,由于都是从左往右结合,所以在下面的声明中,在应用方括号之前,*先与rusks结合。因此rusks是一个指向数组的指针: int (* rusks)[10]。  3.[]和()都是从左往右结合。因此下面声明的goods是一个由12个内含50个int类型值的数组组成的二维数组,不是一个由50个内含12个int类型值的数组组成的二维数组: int goods[12][50]。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 指针 多维数组指针","slug":"C-指针-多维数组指针","permalink":"https://tomsworkspace.github.io/tags/C-%E6%8C%87%E9%92%88-%E5%A4%9A%E7%BB%B4%E6%95%B0%E7%BB%84%E6%8C%87%E9%92%88/"}]},{"title":"C语言数据存储类别","slug":"C语言数据存储类别","date":"2020-02-24T12:31:05.000Z","updated":"2024-10-11T18:29:55.287Z","comments":true,"path":"2020/02/24/C语言数据存储类别/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E8%AF%AD%E8%A8%80%E6%95%B0%E6%8D%AE%E5%AD%98%E5%82%A8%E7%B1%BB%E5%88%AB/","excerpt":"","text":"C语言存储类别与变量存储类别相关的C语言关键字 关键字:auto extern static register _Thread_local 如何区别变量的存储类别 内存用于储存内存中的数据,数据的存储类别取决于它的存储期,作用域和链接。  存储期可以是静态的,自动的或动态分配的。如果是静态存储期,在程序开始执行时分配内存,并在程序运行时都存在。如果是自动存储期,在程序进入变量定义所在的块时分配内存,在离开块时释放内存。如果是动态分配存储期,在调用malloc()(或相关函数)时分配内存,在调用free()时释放内存。  作用域决定程序的那些部分可以访问某数据。定义在所有函数在之外的变量具有文件作用域,对位于该变量声明之后的所有函数可见,定义在块或作为函数形参内的变量具有块作用域,只对块以及它包含的嵌套块可见。  链接描述定义在程序某翻译单元中的变量可被链接的程度。具有块作用域的变量是局部变量,无链接。具有文件作用域的变量可以是内部链接或者是外部链接。内部链接意味着只有其定义所在的文件才能使用该变量,外部链接意味着其他文件也可以使用该变量。 变量存储类别 下面是C语言中变量可能的存储类别,包括线程概念。 存储类别 存储期 作用域 链接 声明方式 自动 自动 块 无 在块中不带存储类别说明符或带auto存储类别说明符声明的变量(或作为函数头中的形参)。如果未初始化,它的值是未定的。 寄存器 自动 块 无 在块中带register存储类别说明符声明的变量。如果未初始化,他的值是未定的。 静态,无链接 静态 块 无 在块中带static存储类别说明符声明的变量。如果未初始化,字节都被设置为0。 静态,外部链接 静态 文件 外部 在所有函数外部且没有使用staatic存储类别说明符声明的变量。如果未初始化,字节都被设置为0。 静态,内部链接 静态 文件 内部 在所有函数外且使用存储类别说明符static声明的变量。如果未初始化,字节被设置为0。 线程,外部链接 线程 文件 外部 在所有块的外部,使用关键字_Thread_local。如果未初始化,字节被设置为0。 线程,内部链接 线程 文件 内部 在所有块的外部,使用关键字static和_Thread_local。如果未初始化,字节被设置为0。 线程,无链接 线程 块 无 在块中,使用关键字static和_Thread_local。如果未初始化,字节被设置为0。  注意:关键字extern只能用来再次声明在别处已经定义过的变量。在函数外部定义变量,该变量具有外部链接属性。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 存储类别 作用域 链接 存储期","slug":"C-存储类别-作用域-链接-存储期","permalink":"https://tomsworkspace.github.io/tags/C-%E5%AD%98%E5%82%A8%E7%B1%BB%E5%88%AB-%E4%BD%9C%E7%94%A8%E5%9F%9F-%E9%93%BE%E6%8E%A5-%E5%AD%98%E5%82%A8%E6%9C%9F/"}]},{"title":"C语言缓冲区","slug":"C语言缓冲区","date":"2020-02-24T12:30:53.000Z","updated":"2024-10-11T18:29:55.288Z","comments":true,"path":"2020/02/24/C语言缓冲区/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E8%AF%AD%E8%A8%80%E7%BC%93%E5%86%B2%E5%8C%BA/","excerpt":"","text":"C语言缓冲区 在编写C语言的交互式程序时,在和程序进行交互的过程中,不知不觉地用使用到了缓冲区的概念。下面来了解一下C语言的缓冲区。 为什么要有缓冲区 首先,把若干字符作为一个块传输比逐个发送字符节省时间;其次,如果用户打错字符,可以直接通过键盘修改错误。当最后按下Enter键时,发送的才是正确的输入。 缓冲区的分类 C语言在不同的地方根据需要使用不同的缓冲区。缓冲区分为两类:完全缓冲I/O和行缓冲I/O。完全缓冲是指的是当前缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是512字节和4096字节。行缓冲指的是在出现换行时刷新缓冲区。键盘输入通常是行缓冲输入,所以在按下Enter键时才刷新缓冲区。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 缓冲区","slug":"C-缓冲区","permalink":"https://tomsworkspace.github.io/tags/C-%E7%BC%93%E5%86%B2%E5%8C%BA/"}]},{"title":"C语言关键字和保留标识符","slug":"C语言关键字和保留标识符","date":"2020-02-24T12:30:39.000Z","updated":"2024-10-11T18:29:55.285Z","comments":true,"path":"2020/02/24/C语言关键字和保留标识符/","link":"","permalink":"https://tomsworkspace.github.io/2020/02/24/C%E8%AF%AD%E8%A8%80%E5%85%B3%E9%94%AE%E5%AD%97%E5%92%8C%E4%BF%9D%E7%95%99%E6%A0%87%E8%AF%86%E7%AC%A6/","excerpt":"","text":"C语言关键字和保留标识符关键字 关键字是C语言的词汇。它们对于C语言比较特殊,不能用他们作为标识符(如,变量名)。许多关键字用于指定不同的类型。如int。还有一些(如if)语句用于控制程序中语句的执行顺序。下表列出了所有的C语言关键字,包括C11标准中出现的。 关键字 标准 描述 auto K&R C 表明有意覆盖一个外部变量定义或者强调不要把该变量改为其他存储类别 extern K&R C 显式声明一个定义与其他文件中的外部变量 short K&R C 声明短整型数据 while K&R C 程序循环 break K&R C 跳出循环语句 float K&R C 声明单精度的浮点数 signed C90 声明带符号的数据类型 _Alignas C11 用于指明内存对齐相关 _Alignof C11 指明内存对齐相关 sizeof K&R C 返回指向的数据对象或类型所占空间的大小,以字节(byte)为单位 for K&R C 程序循环 case K&R C 分支语句 char K&R C 声明单字符型变量 goto K&R C 程序跳转到指定的位置 static K&R C 声明具有静态存储期的数据 _Atomic C11 并发编程时声明一个原子类型的对象 const C90 声明一个不能通过赋值,递增或递减来修改的对象 if K&R C 条件语句 struct K&R C 声明结构体变量 _Bool C11 声明布尔类型变量 continue K&R C 跳出当前循环,开始下一次循环 inline C99 声明内联类型 switch K&R C 分支语句 _Complex C11 声明复数类型 default K&R C 分支语句 int K&R C 声明整形变量 typedef K&R C 为对象创建别名 _Generic C11 实现泛型选择 do K&R C 循环语句 long K&R C 声明长整型 union K&R C 声明联合体变量 _Imaginary C11 声明虚数对象 double K&R C 声明双精度浮点数 register K&R C 声明寄存器类型的变量 unsigned K&R C 声明无符号类型变量 _Noreturn C11 设置调用函数后不返回 else K&R C 条件语句 restrict K&R C 用于编译器优化,只能用于指针,表明指针是访问数据对象唯一的方式 void K&R C 声明无类型的变量 _Static_assert C11 声明编译时检查的断言 enum C90 声明枚举类型 return K&R C 函数返回 volitile C90 用于编译器优化,告知计算机,代理(不是变量坐所在的程序)可以改变该变量的值 _Thread_local C11 声明属于创建线程私有的线程局部数据变量  将所有关键字分一下类大概可以分为如下几类 声明数据类型,包括void,int, short,float,signed,unsigned,char,struct,union,_Bool,_Complex,long,_Imaginary,double,enum。 存储类别说明符,包括auto,register,static,extern,Thread_local,typedef。 程序控制,包括while,do,break,case,for,goto,if,continue,switch,default,_Noreturn,return,else。 类型限定,包括const,volatile,restrict,_Atomic。 其他,包括_Alignas,_Alignof,sizeof,_Generic,_Static_assert。 保留标识符 如果使用关键字不当,如用关键字作为变量名,编译器会将其视为语法错误。还有一些保留标识符。C语言已经指定了它们的用途或保留它们的使用权,如果你使用这些标识符来表示其它意思会导致一些问题。因此,尽管他们也是有效的名称,不会引起语法错误,也不能随意使用。保留标识符包括那些以下划线字符开头的标识符和标准库函数名,如printf()。","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"C 关键字 保留标识符","slug":"C-关键字-保留标识符","permalink":"https://tomsworkspace.github.io/tags/C-%E5%85%B3%E9%94%AE%E5%AD%97-%E4%BF%9D%E7%95%99%E6%A0%87%E8%AF%86%E7%AC%A6/"}]},{"title":"C语言中输入输出所有格式控制符","slug":"C语言中输入输出所有格式控制符","date":"2020-01-16T09:02:06.000Z","updated":"2024-10-11T18:29:55.283Z","comments":true,"path":"2020/01/16/C语言中输入输出所有格式控制符/","link":"","permalink":"https://tomsworkspace.github.io/2020/01/16/C%E8%AF%AD%E8%A8%80%E4%B8%AD%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA%E6%89%80%E6%9C%89%E6%A0%BC%E5%BC%8F%E6%8E%A7%E5%88%B6%E7%AC%A6/","excerpt":"","text":"C语言中输入输出所有格式控制符 最近在重温C语言,发现C语言的输入输出函数scanf和printf函数在控制输入输出时有许多控制符来控制输入输出数据的格式。于是就打算来整理一下。 参考百度百科词条  scanf()是C语言中的一个输入函数。与printf函数一样,都被声明在头文件stdio.h里。它是格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。  printf命令的作用是格式化输出函数,一般用于向标准输出设备按规定格式输出信息。printf()函数的调用格式为:printf(”<格式化字符串>”, <参量表>)。printf()是C语言标准库函数,在 stdio.h 中定义。输出的字符串除了可以使用字母、数字、空格和一些数字符号以外,还可以使用一些转义字符表示特殊的含义。 函数定义 int printf(char *format…); int scanf(const char * restrict format,…); 函数返回值 printf 函数的返回值为printf实际控制输出的字符数。 scanf函数返回成功读入的数据项数,读入数据时遇到了“文件结束”则返回EOF。 格式控制字符串format printf的格式控制字符串format组成如下: %[flags][width][.prec][length]type  即:%[标志][最小宽度][.精度][类型长度]类型控制符 详解见下文。 用法详解 通常意义上format的格式如下:[]里的内容表示可选,即可带可不带。 %[flags][width][.prec][length]type  规定输出数据的格式,具体如下:[1] 类型控制符type type的字符用于规定输出数据的类型,含义如下: 字符 对应数据类型 含义 d / i int 接受整数值并将它表示为有符号的十进制整数,i是老式写法 o unsigned int 无符号8进制整数(不输出前缀0) u unsigned int 无符号10进制整数 x / X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF(不输出前缀0x) f(lf) double 单精度浮点数和双精度浮点数用f(lf 在C99开始加入标准,意思和 f 相同) e / E double 科学计数法表示的数,基数为10,此处”e”的大小写代表在输出时用的”e”的大小写 a /A double 16进制科学计数法表示的数,基数为2,以p表示,以16进制输出,此处”a”的大小写代表在输出时用的”p”的大小写 g / G double 有效位数,如:%.8g表示单精度浮点数保留8位有效数字 c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符 s / S char * / wchar_t * 字符串。输出字符串中的字符直至字符串中的空字符(字符串以’\\0’结尾,这个’\\0’即空字符) p void * 以16进制形式输出指针 n int * 到此字符之前为止,一共输出的字符个数,不输出文本 % 无输入 不进行转换,输出字符‘%’(百分号)本身 m 无 打印errno值对应的出错内容,(例: printf(“%m\\n”); )  注:%g、%G在小数点位数四位或指数大于等于精度时用%e、%E,否则用%f。 标志flags flags规定输出样式,取值和含义如下: 字符 字符名称 说明 - 减号 左对齐,右边填充空格(默认右对齐) + 加号 在数字前增加符号 + 或 - 0 数字零 将输出的前面补上0,直到占满指定列宽为止(不可以搭配使用”-“) 空格 输出值为正时加上空格,为负时加上负号 # 井号 type是o、x、X时,增加前缀0、0x、0X;type是e、E、f、g、G时,一定使用小数点;type是g、G时,尾部的0保留 示例:printf("%5d\\n",1000); //默认右对齐,左边补空格printf("%-5d\\n",1000); //左对齐,右边补空格printf("%+d %+d\\n",1000,-1000); //输出正负号printf("% d % d\\n",1000,-1000);//正号用空格替代,负号输出printf("%x %#x\\n",1000,1000); //输出0xprintf("%.0f %#.0f\\n",1000.0,1000.0)//当小数点后不输出值时依然输出小数点printf("%g %#g\\n",1000.0,1000.0); //保留小数点后后的0printf("%05d\\n",1000); //前面补0 输出最小宽度width 用于控制显示数值的宽度,取值和含义如下:  n(n=1,2,3,4,5,6…): 宽度至少为n位,不够以空格填充。 &esmp;* 格式列表中,下一个参数还是width  width是一个可选的指定最小值字段宽度的十进制数字字符串。如果转换值字符少于字段宽度,该字段将从左到右按指定的字段宽度填充。如果指定了左边调整选项,字段将在右边填充。如果转换结果宽于字段宽度,将扩展该字段以包含转换后的结果。不会发生截断。然而,小的精度可能导致在右边发生截断。 精度.prec 用于控制小数点后面的位数,取值和含义如下:  无按缺省精度显示0 当type=d,i,o,u,x时,没有影响; type=e,E,f时,不显示小数点 n(n=1,2,3…) 当type=e,E,f时表示的最大小数位数; type=其他,表示显示的最大宽度 prec是指可选的精度。精度是一个.(点)后跟十进制数字字符串。如果没有给出精度,按 0(零)对待。精度指定: * d、o、i、 u、x 或 X 转换的最少数字显示位数。 * e 和 f 转换的基数字符后的最少数字显示位数。 * g 转换的最大有效数字位数。 * s 转换中字符串的最大打印字节数目。 类型长度length 类型长度指明待输出数据的长度。因为相同类型可以有不同的长度,比如整型有16bits的short int,32bits的int,也有64bits的long int,浮点型有32bits的单精度float和64bits的双精度double。为了指明同一类型的不同长度,类型长度(length)应运而生,成为格式控制字符串的一部分。 length 描述 h 参数被解释为短整型或无符号短整型(仅适用于整数说明符:i、d、o、u、x 和 X)。 l 参数被解释为长整型或无符号长整型,适用于整数说明符(i、d、o、u、x 和 X)及说明符 c(表示一个宽字符)和 s(表示宽字符字符串)。 L 参数被解释为长双精度型(仅适用于浮点数说明符:e、E、f、g 和 G)。  根据不同的 format 字符串,函数可能需要一系列的附加参数,每个参数包含了一个要被插入的值,替换了 format 参数中指定的每个 % 标签。参数的个数应与 % 标签的个数相同。 [2] 转义序列 这些转义序列在字符串中会被自动转换为相应操作命令。使用的常见转义字符如下: 符号 意义 符号 意义 \\a 铃声(提醒) \\b Backspace \\f 换页 \\n 换行 \\r 回车 \\t 水平制表符 \\v 垂直制表符 \\’ 单引号 \\” 双引号 \\\\ 反斜杠 ? 文本问号 \\ooo(例如\\024) ASCII字符(OCX) \\xhh (例如:\\x20) ASCII字符(HEX) \\xhhhh 宽字符(2字节HEX)  例如,WCHAR f = L’\\x4e00’ 或 WCHAR b[] = L”The Chinese character for one is \\x4e00”。 [1]Brian W.Kernighan,Dennis M.Ritchie.C程序设计语言.中国:机械工业出版社,2004年1月1日:221-222[2]Stephen Prata.C Primer Plus(第五版):人民邮电出版社,2005年2月1日","categories":[{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"}],"tags":[{"name":"printf scanf 输入输出 C语言 格式控制","slug":"printf-scanf-输入输出-C语言-格式控制","permalink":"https://tomsworkspace.github.io/tags/printf-scanf-%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA-C%E8%AF%AD%E8%A8%80-%E6%A0%BC%E5%BC%8F%E6%8E%A7%E5%88%B6/"}]},{"title":"使用免费CDN服务加速Github博客","slug":"使用免费CDN服务加速Github博客","date":"2020-01-15T10:58:14.000Z","updated":"2024-10-11T18:29:55.319Z","comments":true,"path":"2020/01/15/使用免费CDN服务加速Github博客/","link":"","permalink":"https://tomsworkspace.github.io/2020/01/15/%E4%BD%BF%E7%94%A8%E5%85%8D%E8%B4%B9CDN%E6%9C%8D%E5%8A%A1%E5%8A%A0%E9%80%9FGithub%E5%8D%9A%E5%AE%A2/","excerpt":"","text":" 最近发现部署在github上的个人博客网站访问加载的时间越来越久了,本身Github就已经很慢了,这个加载的速度令人难以接受,特别是对于有图片等比较大的文件需要加载的时候。于是开始考虑有没有什么方法能加速一下。  最好的方法当然是多花点钱,买个服务器把博客网站部署上去,不过哪有免费的东西用着舒服呢。于是发现了一个叫做CDN的加速方式,可以对资源访问进行加速。其实对于个人站点来说,只要能加速网站上的静态文件,比如图片、js文件、css文件,网站的访问速度就会大大的提升。 CDN加速的原理 CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。——百度百科 七牛云图床 七牛云就是一个这样的工具。可以把你需要加速的图片放到上面去。也提供免费的空间供你使用,不过需要注册和实名认证。感觉不是太友好。你也可以直接把你的博客网站搬上去。原理都是一样的。  配置方法 jsDelivr jsDelivr的宗旨是为开发者提供免费公共 CDN 加速服务。通过使用jsDelivr,不移动你需要加速的内容,可以直接加速Github项目上的资源。这样可以将个人博客的访问速度大大提升。关键是配置简单,而且完全免费。大力推荐。  jsDelivr介绍  配置方法 https://blog.csdn.net/larpland/article/details/101349605 https://blog.csdn.net/qq_36759224/article/details/86936453 https://www.hojun.cn/2019/01/18/ck4wa261r005jdcvas9gv7evx/ 第一步: 发布你的github仓库 把你博客里用到的图片放到github仓库里。可以另外建立一个仓库来保存用到的图片,这样方便管理。  在仓库界面下,点release发布  自定义发布版本号 第二步:在文章中引用你需要的资源 使用方法:https://cdn.jsdelivr.net/gh/你的用户名/你的仓库名@发布的版本号/文件路径 例如: https://cdn.jsdelivr.net/gh/TRHX/CDN-for-- itrhx.com@1.0/images/trhx.png https://cdn.jsdelivr.net/gh/TRHX/[email protected]/css/style.css https://cdn.jsdelivr.net/gh/moezx/[email protected]//Sakurasou.mp4  注意:版本号不是必需的,是为了区分新旧资源,如果不使用版本号,将会直接引用最新资源,除此之外还可以使用某个范围内的版本,查看所有资源等,具体使用方法如下: // 加载任何Github发布、提交或分支 https://cdn.jsdelivr.net/gh/user/repo@version/file // 加载 jQuery v3.2.1 https://cdn.jsdelivr.net/gh/jquery/[email protected]/dist/jquery.min.js // 使用版本范围而不是特定版本 https://cdn.jsdelivr.net/gh/jquery/[email protected]/dist/jquery.min.js https://cdn.jsdelivr.net/gh/jquery/jquery@3/dist/jquery.min.js // 完全省略该版本以获取最新版本 https://cdn.jsdelivr.net/gh/jquery/jquery/dist/jquery.min.js // 将“.min”添加到任何JS/CSS文件中以获取缩小版本,如果不存在,将为会自动生成 https://cdn.jsdelivr.net/gh/jquery/[email protected]/src/core.min.js // 在末尾添加 / 以获取资源目录列表 https://cdn.jsdelivr.net/gh/jquery/jquery/","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"免费CDN服务","slug":"免费CDN服务","permalink":"https://tomsworkspace.github.io/tags/%E5%85%8D%E8%B4%B9CDN%E6%9C%8D%E5%8A%A1/"},{"name":"加速个人博客","slug":"加速个人博客","permalink":"https://tomsworkspace.github.io/tags/%E5%8A%A0%E9%80%9F%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"name":"CDN","slug":"CDN","permalink":"https://tomsworkspace.github.io/tags/CDN/"},{"name":"github","slug":"github","permalink":"https://tomsworkspace.github.io/tags/github/"},{"name":"jsdelivr","slug":"jsdelivr","permalink":"https://tomsworkspace.github.io/tags/jsdelivr/"}]},{"title":"如何在代码中计算时钟周期数","slug":"如何在代码中计算时钟周期数","date":"2020-01-02T12:33:57.000Z","updated":"2024-10-11T18:29:55.324Z","comments":true,"path":"2020/01/02/如何在代码中计算时钟周期数/","link":"","permalink":"https://tomsworkspace.github.io/2020/01/02/%E5%A6%82%E4%BD%95%E5%9C%A8%E4%BB%A3%E7%A0%81%E4%B8%AD%E8%AE%A1%E7%AE%97%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F%E6%95%B0/","excerpt":"","text":"如何在代码中计算代码执行的时钟周期数 有的时候,需要在代码中获取代码运行的时钟周期数。那么,怎么获取到某段代码在运行时的时钟周期数呢? 通过获取程序运行时间 在代码中直接计算一个程序的时钟周期是不容易的。但是计算一个程序的执行时间却是比较容易的。编程语言和操作系统都提供有相关的封装好的接口,可以很方便地调用来得到程序的运行时间。这方面的参考资料很多,我就不去介绍了。  如何获取程序的执行时间? Linux环境 || Windows环境  下面来介绍一下怎么通过获取的程序执行时间来得到十周周期数。 CPU时钟周期:通常为节拍脉冲或T周期,即主频的倒数,它是CPU中最小的时间单位,每个动作至少需要一个时钟周期。而主频又与与具体的时间有关。CPU的主频表示每秒进行的时钟周期数。那么运行时间与时钟周期数有如下的计算方式:  根据获取的运行时间和CPU的主频可以计算出程序的时钟周期数。 直接计算时钟周期数 也有直接计算时钟周期数的方法。为了给计时测量提供更高的准确度,很多处理器还包含一个运行在始终周期级别的计时器,它是一个特殊的寄存器,每个时钟周期它都会自动加1。这个周期计数器呢,是一个64位无符号数,直观理解,就是如果你的处理器是1GHz的,那么需要570年,它才会从2的64次方绕回到0,所以你大可不必考虑溢出的问题。但是这种方法是依赖于硬件的。首先,并不是每种处理器都有这样的寄存器的;其次,即使大多数都有,实现机制也不一样,因此,我们无法用统一的,与平台无关的接口来使用它们。于是就要使用汇编来处理。在这里给出一个C语言嵌入汇编的例程。 unsigned long long int begin,end,total=0; static __inline__ unsigned long long rdtsc(void) { unsigned hi, lo; __asm__ __volatile__ ("rdtsc" : "=a"(lo),"=d"(hi)); return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 ); } int main(){ begin=rdtsc(); //这里调用要测试的函数 end=rdtsc(); total = end – begin; // total即為 cpu clock cycle } 存在的问题对于第一种方法  对于先计时再计算时钟周期数,除开计时器本身可能存在的精度问题。另外,时间 = 周期数 / 频率,由于频率可能会变(比如我的笔记本的 CPU 通常半速运行在 800MHz,繁忙的时候全速运行在 1.6GHz),那么测得的时间也就不准确了。计算的时钟周期就不准。 对于第二种方法 原文链接  在多核时代,RDTSC 指令的准确度大大削弱了,原因有三: 不能保证同一块主板上每个核的 TSC 是同步的; CPU 的时钟频率可能变化,例如笔记本电脑的节能功能; 乱序执行导致 RDTSC 测得的周期数不准,这个问题从 Pentium Pro 时代就存在。  在多核下,这两次执行可能会在两个CPU上发生,而这两个CPU的计数器的初值不一定相同(由于完成上电复位的准确时机不同),(有办法同步,见如何同步),那么就导致结果包含了这个误差,这个误差可正可负,取决于先执行的那块CPU 的时钟计数器是超前还是落后。 &emsp乱序执行这个问题比较简单,但意义深远。在现代 CPU 的复杂架构下,测量几条或几十条指令的耗时是无意义的,因为观测本身会干扰 CPU 的执行(cache, 流水线, 多发射,乱序, 猜测)。要么我们以更宏观的指标来标示性能;要么用专门的手段来减小对观测结果的影响。  当然,无论怎样,都不可能测得一个程序的准确时钟周期数。但是在允许一定误差存在的条件下,这些方法是有效的,根据场景合理的选择需要的方法。","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"计算代码时钟周期数","slug":"计算代码时钟周期数","permalink":"https://tomsworkspace.github.io/tags/%E8%AE%A1%E7%AE%97%E4%BB%A3%E7%A0%81%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F%E6%95%B0/"}]},{"title":"Huffman算法实现文件压缩解压","slug":"Huffman算法实现文件压缩解压","date":"2020-01-02T12:33:11.000Z","updated":"2024-10-11T18:29:55.296Z","comments":true,"path":"2020/01/02/Huffman算法实现文件压缩解压/","link":"","permalink":"https://tomsworkspace.github.io/2020/01/02/Huffman%E7%AE%97%E6%B3%95%E5%AE%9E%E7%8E%B0%E6%96%87%E4%BB%B6%E5%8E%8B%E7%BC%A9%E8%A7%A3%E5%8E%8B/","excerpt":"","text":"Huffman算法实现文件压缩解压前言 哈夫曼编码是一种贪心算法和二叉树结合的字符编码方式,具有广泛的应用背景,最直观的是文件压缩。下面讲述如何用哈夫曼编解码实现文件的压缩和解压。 哈夫曼编码的概念 哈夫曼树又称作最优树,是一种带权路径长度最短的树,而通过哈夫曼树构造出的编码方式称作哈夫曼编码。也就是说哈夫曼编码是一个通过哈夫曼树进行的一种编码,一般情况下,以字符 “0” 与 “1” 表示。编码的实现过程很简单,只要实现哈夫曼树,通过遍历哈夫曼树,这里我们从根节点开始向下遍历,如果下个节点是左孩子,则在字符串后面追加 “0”,如果为其右孩子,则在字符串后追加 “1”。结束条件为当前节点为叶子节点,得到的字符串就是叶子节点对应字符的编码。 哈夫曼编码用于文件压缩的原理 我们都知道根据人类使用文字对应的每个字符都是有特定的频率的。比如说英文,一般来说字母a或者e的使用频率很高。如果我们能给出现频率最高的字符很短的编码,出现最少的字符最长的编码,而且保证每个编码都不是任意一个编码的前缀码。为什么要保证这样呢?如果任意一个编码都不是其他编码的前缀码,那么只要读到一个对应字符的编码那么它对应的字符只有一个,不会产生歧义,这样能保证根据压缩的编码来解压不会出现错误。如果我们用这样的编码来对一个文件进行压缩,那么出现最多的字符将会有最短的编码,可以节省很多的空间,将一个大的文件压缩成一个比较小的文件。而哈夫曼树就是一个可以用来生成这样一种编码的树,以树的叶子节点代表一个字符,从根节点到叶节点的路径形成一个编码,左子树表示比特1,右子树表示比特0,可以交换。这样每个字符对应一个唯一的编码而且任意一个编码都不是其他编码的前缀码。如果我们要压缩一个文件,需要先统计这个字符文件中每个字符出现的频率,然后根据频率来生成一颗哈夫曼树,使频率最高的字符对应于最短的编码。然后根据生成的编码对文件进行压缩。也就是把每个字符替换成其对应的编码来保存。生成后的压缩文件会比原来的文件小得多。然后有根据压缩的编码来对压缩的文件进行解压缩,也就是压缩的反过程。这样又可以恢复出原本的文件了。起到了文件压缩节省空间,同时还增加了文件的保密性。也可以看成是文件的加密和解密。 哈夫曼树实现及其效率 根据贪心算法的思想实现,把字符出现频率较多的字符用稍微短一点的编码,而出现频率较少的字符用稍微长一点的编码。哈夫曼树就是按照这种思想实现,下面将举例分析创建哈夫曼树的具体过程。下面表格的每一行分别对应字符及出现频率,根据这些信息就能创建一棵哈夫曼树。 字符 出现频率 编码 总二进制位数 a 500 1 500 b 250 01 500 c 120 001 360 d 60 0001 240 e 30 00001 150 f 20 00000 100  如下图,将每个字符看作一个节点,将带有频率的字符全部放到优先队列中,每次从队列中取频率最小的两个节点 a 和 b(这里频率最小的 a 作为左子树),然后新建一个节点R,把节点设置为两个节点的频率之和,然后把这个新节点R作为节点A和B的父亲节点。最后再把R放到优先队列中。重复这个过程,直到队列中只有一个元素,即为哈夫曼树的根节点。  由上分析可得,哈夫曼编码的需要的总二进制位数为 500 + 500 + 360 + 240 + 150 + 100 = 1850。上面的例子如果用等长的编码对字符进行压缩,实现起来更简单,6 个字符必须要 3 位二进制位表示,解压缩的时候每次从文本中读取 3 位二进制码就能翻译成对应的字符,如 000,001,010,011,100,101 分别表示 a,b,c,d,e,f。则需要总的二进制位数为 (500 + 250 + 120 + 60 + 30 + 20)* 3 = 2940。对比非常明显哈夫曼编码需要的总二进制位数比等长编码需要的要少很多,这里的压缩率为 1850 / 2940 = 62%。哈夫曼编码的压缩率通常在 20% ~90% 之间。 代码实现","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"Huffman编码","slug":"Huffman编码","permalink":"https://tomsworkspace.github.io/tags/Huffman%E7%BC%96%E7%A0%81/"},{"name":"文件解压缩","slug":"文件解压缩","permalink":"https://tomsworkspace.github.io/tags/%E6%96%87%E4%BB%B6%E8%A7%A3%E5%8E%8B%E7%BC%A9/"}]},{"title":"http/https代理服务器的代码实现","slug":"http-https代理服务器的代码实现","date":"2019-12-22T08:57:42.000Z","updated":"2024-10-11T18:29:55.315Z","comments":true,"path":"2019/12/22/http-https代理服务器的代码实现/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/22/http-https%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%9A%84%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/","excerpt":"","text":"http/https代理服务器的代码实现代理服务器工作原理 代理服务器作为一种既是服务器又是客户机的中间程序,主要用于转发客户系统的网络访问请求。但是,代理服务器不只是简单地向真正的因特网服务器转发请求,它还可以控制用户的行为,对接收到的客户请求进行决策,并根据过滤规则对用户请求进行过滤。  通过代理服务器,网络管理员可以实现比用包过滤路由器更严格的安全策略。不同于使用通用的包过滤路由器来管理通过防火墙的因特网服务流向,代理服务器通过在网关上为每项需要的应用安装专用的代码(代理服务)来工作。如果网络管理员没有为某一特殊服务安装代理服务代码,该服务就不会被支持,也不会通过防火墙转发相应的客户请求。并且,这种代理服务器码能被配置成仅支持某项服务的网络管理员认为可以接受的那部分特征,而不支持其他的特征。 代理服务器主要功能 代理服务器具有许多功能。对于我们个人用户而言,通过代理上网,能让我们访问一些直接访问会比较慢的网站,比如互联网用户访问教育网的网站。对于单位而言,内部使用代理可以预先过滤一些病毒,保障上网的安全,还能有效地进行访问控制、网速限制,上网监控等等。  以下介绍代理服务器的基本功能: (1)一个lP地址或Internet帐户供多个用户同时使用  在目前情况下,IP地址是Internet中有限的宝贵资源,如果将这些IP地址仅仅用于单个的请求Internet访问的用户,不能不说是一种资源浪费。使用代理服务器可以做到通过一个IP同时向多个用户提供Internet的访问,对于通过电话拨号连通Internet的内部网络,则可以实现利用一条电话线,一个modem和一个Internet帐户,让内部网络上所有用户同时访问Internet,这样就充分利用了IP地址资源。 (2)缓存功能,可以降低费用,提高速度  安装时,代理服务器会在硬盘上开出一块磁盘空间作为缓存区,将代理用户从Internet上接收的内容下载一份保存起来,当再有用户访问同样内容时,就直接从缓存区传送给用户,而不再从Internet上寻找。代理服务器的这项功能可以大大地提高访问速度,同时也降低了通信费用,是一项相当重要的功能。 (3)对内部网络用户进行权限和信息流量计费管理  通过代理服务器,网管员在提供Internet服务时,可以容易地对内部网络用户进行访问权限和信息流量计费的管理。网管员不但能够做到只允许被授权的局域网用户访问Internet,还能够控制这些用户在哪些时间、使用哪台计算机访问哪些类型的Internet服务。对于已经获准访问的Internet的用户,网管员还能够按照多种方式进行信息流量的计费管理,如:按照个人计费、按照部门所属计算机计费等,为网络管理带来了极大的方便。 (4)对进入内部网络的Internet信息实施监控和过滤  为了避免那些与业务无关的信息进入内部网络浪费通信资费,各个机构对允许访问的内容往往有一些相应的规定。通过代理服务器,网管员不但可以采取过滤的方法简便地控制从Internet流入内部网络的信息内容,还能对用户访问Internet的情况进行实时监控和建立监查日志存档备查。 实现功能:服务器端 (1)在指定端口(例如 9080)接收来自客户的 http/https 请求并且根据其中的 URL 地址访问该地址所指向的 http/https 服务器(原服务器),接收服务器的响应报文,并将响应报文转发给对应的客户进行浏览。 (2)支持日志功能,可以将用户的访问目标和内容记录到指定的文件。 (3)网站过滤:允许/不允许重点内容访问某些网站 (4)多级代理功能,可以指定上级代理服务器实现多级代理 客户端  实现设置IE代理的GUI界面 实现步骤(1)客户端界面通过修改注册表实现代理设置(2)代理服务器主线程等待客户端连接。(3)代理服务器接收客户端发送的 TCP 请求报文,建立线程处理并解析 HTTP 头部(method, url, host 等信息)。(4)在建立的线程中建立服务器到目标地址的socket连接。(5)开启两个线程来处理上行和下行的流量,只负责单纯的转发。(6)客户端(即浏览器)收到代理服务器返回的报文,解析并显示。 代码idea java项目","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"http/https代理服务器","slug":"http-https代理服务器","permalink":"https://tomsworkspace.github.io/tags/http-https%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"}]},{"title":"通过修改注册表来更改IE代理","slug":"通过修改注册表来更改IE代理","date":"2019-12-22T08:16:39.000Z","updated":"2024-10-11T18:29:55.335Z","comments":true,"path":"2019/12/22/通过修改注册表来更改IE代理/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/22/%E9%80%9A%E8%BF%87%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%E6%9D%A5%E6%9B%B4%E6%94%B9IE%E4%BB%A3%E7%90%86/","excerpt":"","text":"通过修改注册表来更改IE浏览器的代理设置一、什么是注册表 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”,也可以说是一个非常巨大的树状分层结构的数据库系统。 注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息,它包括了计算机的硬件配置,包括自动配置的即插即用的设备和已有的各种设备说明、状态属性以及各种状态信息和数据。利用一个功能强大的注册表数据库来统一集中地管理系统硬件设施、软件配置等信息,从而方便了管理,增强了系统的稳定性。 二、注册表的功能 注册表是windows操作系统中的一个核心数据库,其中存放着各种参数,直接控制着windows的启动、硬件驱动程序的装载以及一些windows应用程序的运行,从而在整个系统中起着核心作用。这些作用包括了软、硬件的相关配置和状态信息,比如注册表中保存有应用程序和资源管理器外壳的初始条件、首选项和卸载数据等,联网计算机的整个系统的设置和各种许可,文件扩展名与应用程序的关联,硬件部件的描述、状态和属性,性能记录和其他底层的系统状态信息,以及其他数据等。 具体来说,在启动Windows时,Registry会对照已有硬件配置数据,检测新的硬件信息;系统内核从Registry中选取信息,包括要装入什么设备驱动程序,以及依什么次序装入,内核传送回它自身的信息,例如版权号等;同时设备驱动程序也向Registry传送数据,并从Registry接收装入和配置参数,一个好的设备驱动程序会告诉Registry它在使用什么系统资源,例如硬件中断或DMA通道等,另外,设备驱动程序还要报告所发现的配置数据;为应用程序或硬件的运行提供增加新的配置数据的服务。配合ini文件兼容16位Windows应用程序,当安装—个基于Windows 3.x的应用程序时,应用程序的安装程序Setup像在windows中—样创建它自己的INI文件或在win.ini和system.ini文件中创建入口;同时windows还提供了大量其他接口,允许用户修改系统配置数据,例如控制面板、设置程序等。 如果注册表受到了破坏,轻则使windows的启动过程出现异常,重则可能会导致整个windows系统的完全瘫痪。因此正确地认识、使用,特别是及时备份以及有问题恢复注册表对windows用户来说就显得非常重要。 如何打开注册表 打开注册表的命令是: regedit或regedit.exe、regedt32或regedt32.exe  正常情况下,你可以点击开始菜单当中的运行,然后输入regedit或regedit.exe点击确定就能打开windows操作系统自带的注册表编辑器了,有图慎重提醒,操作注册表有可能造成系统故障,若您是对windows注册表不熟悉、不了解或没有经验的windows操作系统用户建议尽量不要随意操作注册表。  如果上述打开注册表的方法不能使用,说明你没有管理员权限,或者注册表被锁定,如果是没有权限,请寻找电脑管理员帮助解决,如果注册表被锁定,请参照下面的方式进行解锁。 注册表解锁常见的方法: 1:创建一个文本文件,复制以下文字文本内容(注意开头之后第二行一定要是空行并且不可少),选择另存为,文件类型选择所有文件,文件名称为注册表解锁.reg REGEDIT4[HKEY_USERS.DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system"DisableRegistryTools"=dword:00000000] 保存文件到桌面,双击打开桌面上的注册表解锁.reg如下图,点击确定即可。  2:使用第三方工具恢复,比如使用超级兔子或者优化大师这类系统辅助软件,以下以优化大师为例说明:打开优化大师,点击左侧的系统优化,然后选择系统安全优化,点击右侧的更多设置,,取消禁用注册表编辑器项目前面的对勾。  3:利用系统策略编辑器在Windows 2000/XP/2003操作系统下在Windows 2000/XP/2003等操作系统当中,我们可以通过单击 开始-运行,输入gpedit.msc之后点击确定或按回车,打开windows操作系统自带的组策略编辑器。然后,依次展开用户配置-管理模板-系统,双击右侧窗口中的阻止访问注册表编辑工具,在弹出的窗口中选择已禁用,确定后再退出组策略编辑器,即可为注册表解锁。 如何修改浏览器的IE设置 一般来说,可以通过系统系带的图形界面来更改IE的代理服务器设置  对于Win10系统,打开设置,选择网络和internet,再选择左下角的代理就可以进行设置。或者再网络图标点击右键打开网络和Internet设置,选择代理进行设置。  对于更低版本的Windows系统,在网络图标上点击右键打开网络和Internet设置,在搜索栏里搜索代理,就可以进行代理服务器的设置。 也可以直接在浏览器的设置菜单里实现对代理的修改 可是有的时候,我们就需要运用其他方式对代理进行更改。于是可以选择更改注册表来实现ie代理的更改。有两种方法: 通过自动生成注册表文件,再调用reg.exe命令 在说明本文之前,首先说明有比较简单的方法,java程序自动生成注册表文件*.reg,格式为: REGEDIT4[HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings]"ProxyEnable"=dword:00000001"ProxyServer"="localhost:8080""ProxyOverride"="<localhost>" 然后调用,Runtime.getRuntime().exec( “reg.exe import “ + filename); 通过调用registry的api修改 既然目前已经有现成的修改注册表的包jni的registry包,那我们就不费这个事做上面的操作了,registry jar包下载地址,另外发现openorg上也有类似的,但是类名改掉了,感兴趣的同学可以去搜搜。 IE代理服务器对应于注册表中字段:HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings下面的值:ProxyServer,ProxyEnable,ProxyOverrideProxyEnable用来表示是否使用代理,用0,1表示,类型为REG_DWORD,不能为REG_SZProxyServer用来表示代理服务器ip:port,如localhost:8080,类型为REG_SZProxyOverride表示跳过代理的配置,比如跳过本地代理,该值为 原文链接:https://blog.csdn.net/iteye_21060/article/details/82212107 通过bat脚本设置系统代理,然后在java中调用batjava的Runtime.getRuntime().exec(commandStr)可以调用执行cmd指令。通过他来调用运行bat脚本的cmd命令来实现更改。 原文链接:https://blog.csdn.net/baidu_23275675/article/details/84427757 修改注册表带来的一个问题 通过修改注册表来实现对代理设置的更改不会立即生效,这是注册表本身自带的缺陷。对于这个问题也是有解决办法的:如何使更改注册表立即生效","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"修改注册表","slug":"修改注册表","permalink":"https://tomsworkspace.github.io/tags/%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8/"},{"name":"更改IE代理","slug":"更改IE代理","permalink":"https://tomsworkspace.github.io/tags/%E6%9B%B4%E6%94%B9IE%E4%BB%A3%E7%90%86/"},{"name":"IE代理设置","slug":"IE代理设置","permalink":"https://tomsworkspace.github.io/tags/IE%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE/"}]},{"title":"修改Windows系统注册表并使其立即生效","slug":"修改Windows系统注册表并使其立即生效","date":"2019-12-22T07:38:06.000Z","updated":"2024-10-11T18:29:55.324Z","comments":true,"path":"2019/12/22/修改Windows系统注册表并使其立即生效/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/22/%E4%BF%AE%E6%94%B9Windows%E7%B3%BB%E7%BB%9F%E6%B3%A8%E5%86%8C%E8%A1%A8%E5%B9%B6%E4%BD%BF%E5%85%B6%E7%AB%8B%E5%8D%B3%E7%94%9F%E6%95%88/","excerpt":"","text":"修改Windows系统注册表并使其立即生效什么是注册表 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”,也可以说是一个非常巨大的树状分层结构的数据库系统。 注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息,它包括了计算机的硬件配置,包括自动配置的即插即用的设备和已有的各种设备说明、状态属性以及各种状态信息和数据。利用一个功能强大的注册表数据库来统一集中地管理系统硬件设施、软件配置等信息,从而方便了管理,增强了系统的稳定性。 注册表的功能 注册表是windows操作系统中的一个核心数据库,其中存放着各种参数,直接控制着windows的启动、硬件驱动程序的装载以及一些windows应用程序的运行,从而在整个系统中起着核心作用。这些作用包括了软、硬件的相关配置和状态信息,比如注册表中保存有应用程序和资源管理器外壳的初始条件、首选项和卸载数据等,联网计算机的整个系统的设置和各种许可,文件扩展名与应用程序的关联,硬件部件的描述、状态和属性,性能记录和其他底层的系统状态信息,以及其他数据等。 具体来说,在启动Windows时,Registry会对照已有硬件配置数据,检测新的硬件信息;系统内核从Registry中选取信息,包括要装入什么设备驱动程序,以及依什么次序装入,内核传送回它自身的信息,例如版权号等;同时设备驱动程序也向Registry传送数据,并从Registry接收装入和配置参数,一个好的设备驱动程序会告诉Registry它在使用什么系统资源,例如硬件中断或DMA通道等,另外,设备驱动程序还要报告所发现的配置数据;为应用程序或硬件的运行提供增加新的配置数据的服务。配合ini文件兼容16位Windows应用程序,当安装—个基于Windows 3.x的应用程序时,应用程序的安装程序Setup像在windows中—样创建它自己的INI文件或在win.ini和system.ini文件中创建入口;同时windows还提供了大量其他接口,允许用户修改系统配置数据,例如控制面板、设置程序等。 如果注册表受到了破坏,轻则使windows的启动过程出现异常,重则可能会导致整个windows系统的完全瘫痪。因此正确地认识、使用,特别是及时备份以及有问题恢复注册表对windows用户来说就显得非常重要。 如何打开注册表 打开注册表的命令是: regedit或regedit.exe、regedt32或regedt32.exe  正常情况下,你可以点击开始菜单当中的运行,然后输入regedit或regedit.exe点击确定就能打开windows操作系统自带的注册表编辑器了,有图慎重提醒,操作注册表有可能造成系统故障,若您是对windows注册表不熟悉、不了解或没有经验的windows操作系统用户建议尽量不要随意操作注册表。  如果上述打开注册表的方法不能使用,说明你没有管理员权限,或者注册表被锁定,如果是没有权限,请寻找电脑管理员帮助解决,如果注册表被锁定,请参照下面的方式进行解锁。 注册表解锁常见的方法: 1:创建一个文本文件,复制以下文字文本内容(注意开头之后第二行一定要是空行并且不可少),选择另存为,文件类型选择所有文件,文件名称为注册表解锁.reg REGEDIT4[HKEY_USERS.DEFAULT\\Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\system"DisableRegistryTools"=dword:00000000] 保存文件到桌面,双击打开桌面上的注册表解锁.reg如下图,点击确定即可。  2:使用第三方工具恢复,比如使用超级兔子或者优化大师这类系统辅助软件,以下以优化大师为例说明:打开优化大师,点击左侧的系统优化,然后选择系统安全优化,点击右侧的更多设置,,取消禁用注册表编辑器项目前面的对勾。  3:利用系统策略编辑器在Windows 2000/XP/2003操作系统下在Windows 2000/XP/2003等操作系统当中,我们可以通过单击 开始-运行,输入gpedit.msc之后点击确定或按回车,打开windows操作系统自带的组策略编辑器。然后,依次展开用户配置-管理模板-系统,双击右侧窗口中的阻止访问注册表编辑工具,在弹出的窗口中选择已禁用,确定后再退出组策略编辑器,即可为注册表解锁。 修改注册表后立即生效的方法 有时候需要对修改后的注册表使他立即生效。  要让修改后的注册表生效通常有三种方法: 刷新。也就是说修改注册表后可以立即生效(一些修改是可以的)。几个修改注册表后立即生效的刷新方法 重启explorer进程。这也是通常替代重启的最简单的方法(适用绝大多数)。手动&emsp修改注册表后,一般需要重启才能生效,当然你也可以做到不重启就生效,同时按下Ctrl+Shift+Esc组合键,在弹出的Windows任务列表中,选择Explore,单击“结束进程”按钮,接着在弹出的警告对话框中单击“是”,然后单击“Windows任务管理器”的“文件→新任务(运行)”,在弹出的“创建新任务”的“打开”文本框中输入:explorer,回车后“资源管理器”重新载入,同时修改的注册表也会一并生效。 bat批处理脚本 在记事本里,输入以下内容,并保存为Reflash.BAT文件即可! @echo offecho explorer.exe已关闭!taskkill /im explorer.exe /fecho 正在开启explorer.exestart "" "C:\\WINDOWS\\explorer.exe"echo explorer.exe已开启!ping -n 4 127.1>nulexit 语法说明:@echo off 是不显示执行命令echo 后面接显示的文字调用taskkill结束进程 /im指定要终止的进程,后面接进程名 /f强行终止进程echo 后面接显示的文字start 启动,后面接可执行程序及其位置echo 后面接显示的文字ping 是测试连接的命令 -n 4是只发送一次命令,并且延迟3秒 127.1是本机保留IP地址 >nul是把命令重定向到空exit 退出  这个BAT文件,会在双击后,自动结束“explorer进程”,然后又会新建explorer进程来使得修改后的注册表生效。用这个简单的方法基本上可以解决日常注册表的修改! 3、重启。有一些修改是必须要重启计算机的,没有什么其他捷径可言(适用全部)如果以上的方法都没有生效,那么重启是一定可以让你更改的注册表生效的。","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"-修改注册表立即生效","slug":"修改注册表立即生效","permalink":"https://tomsworkspace.github.io/tags/%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%E7%AB%8B%E5%8D%B3%E7%94%9F%E6%95%88/"}]},{"title":"Mips-register","slug":"Mips-register","date":"2019-12-07T06:54:25.000Z","updated":"2024-10-11T18:29:55.304Z","comments":true,"path":"2019/12/07/Mips-register/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/07/Mips-register/","excerpt":"","text":"一. 简介 MIPS有32个通用寄存器($0-$31),各寄存器的功能及汇编程序中使用约定如下:  下表描述32个通用寄存器的别名和用途 REGISTER NAME USAGE $0 $zero 常量0(constant value 0) $1 $at 保留给汇编器(Reserved for assembler) $2-$3 $v0-$v1 函数调用返回值(values for results and expression evaluation) $4-$7 $a0-$a3 函数调用参数(arguments) $8-$15 $t0-$t7 暂时的(或随便用的) $16-$23 $s0-$s7 保存的(或如果用,需要SAVE/RESTORE的)(saved) $24-$25 $t8-$t9 暂时的(或随便用的) $28 $gp 全局指针(Global Pointer) $29 $sp 堆栈指针(Stack Pointer) $30 $fp 帧指针(Frame Pointer) $31 $ra 返回地址(return address) 二. 详细说明 $0: 即$zero,该寄存器总是返回零,为0这个有用常数提供了一个简洁的编码形式。 move $t0,$t1 实际为 add $t0,$0,$t1 使用伪指令可以简化任务,汇编程序提供了比硬件更丰富的指令集。  $1: 即$at,该寄存器为汇编保留,由于I型指令的立即数字段只有16位,在加载大常数时,编译器或汇编程序需要把大常数拆开,然后重新组合到寄存器里。比如加载一个32位立即数需要 lui(装入高位立即数)和addi两条指令。像MIPS程序拆散和重装大常数由汇编程序来完成,汇编程序必需一个临时寄存器来重组大常数,这也是为汇编 保留$at的原因之一。  $2..$3: ($v0-$v1)用于子程序的非浮点结果或返回值,对于子程序如何传递参数及如何返回,MIPS范围有一套约定,堆栈中少数几个位置处的内容装入CPU寄存器,其相应内存位置保留未做定义,当这两个寄存器不够存放返回值时,编译器通过内存来完成。  $4..$7: ($a0-$a3)用来传递前四个参数给子程序,不够的用堆栈。a0-a3和v0-v1以及ra一起来支持子程序/过程调用,分别用以传递参数,返回结果和存放返回地址。当需要使用更多的寄存器时,就需要堆栈(stack)了,MIPS编译器总是为参数在堆栈中留有空间以防有参数需要存储。  $8..$15: ($t0-$t7)临时寄存器,子程序可以使用它们而不用保留。  $16..$23: ($s0-$s7)保存寄存器,在过程调用过程中需要保留(被调用者保存和恢复,还包括$fp和$ra),MIPS提供了临时寄存器和保存寄存器,这样就减少了寄存器溢出(spilling,即将不常用的变量放到存储器的过程),编译器在编译一个叶(leaf)过程(不调用其它过程的过程)的时候,总是在临时寄存器分配完了才使用需要保存的寄存器。  $24..$25: ($t8-$t9)同($t0-$t7)  $26..$27: ($k0,$k1)为操作系统/异常处理保留,至少要预留一个。 异常(或中断)是一种不需要在程序中显示调用的过程。MIPS有个叫异常程序计数器(exception program counter,EPC)的寄存器,属于CP0寄存器,用于保存造成异常的那条指令的地址。查看控制寄存器的唯一方法是把它复制到通用寄存器里,指令mfc0(move from system control)可以将EPC中的地址复制到某个通用寄存器中,通过跳转语句(jr),程序可以返回到造成异常的那条指令处继续执行。MIPS程序员都必须保留两个寄存器$k0和$k1,供操作系统使用。  发生异常时,这两个寄存器的值不会被恢复,编译器也不使用k0和k1,异常处理函数可以将返回地址放到这两个中的任何一个,然后使用jr跳转到造成异常的指令处继续执行。  $28: ($gp)为了简化静态数据的访问,MIPS软件保留了一个寄存器:全局指针gp(global pointer,$gp),全局指针只想静态数据区中的运行时决定的地址,在存取位于gp值上下32KB范围内的数据时,只需要一条以gp为基指针的指令即可。在编译时,数据须在以gp为基指针的64KB范围内。  $29: ($sp)MIPS硬件并不直接支持堆栈,你可以把它用于别的目的,但为了使用别人的程序或让别人使用你的程序, 还是要遵守这个约定的,但这和硬件没有关系。  $30: ($fp)GNU MIPS C编译器使用了帧指针(frame pointer),而SGI的C编译器没有使用,而把这个寄存器当作保存寄存器使用($s8),这节省了调用和返回开销,但增加了代码生成的复杂性。  $31: ($ra)存放返回地址,MIPS有个jal(jump-and-link,跳转并 链接)指令,在跳转到某个地址时,把下一条指令的地址放到$ra中。用于支持子程序,例如调用程序把参数放到$a0~$a3,然后jal X跳到X过程,被调过程完成后把结果放到$v0,$v1,然后使用jr $ra返回。","categories":[{"name":"Computer Architecture","slug":"Computer-Architecture","permalink":"https://tomsworkspace.github.io/categories/Computer-Architecture/"}],"tags":[{"name":"MIPs","slug":"MIPs","permalink":"https://tomsworkspace.github.io/tags/MIPs/"},{"name":"Mips寄存器","slug":"Mips寄存器","permalink":"https://tomsworkspace.github.io/tags/Mips%E5%AF%84%E5%AD%98%E5%99%A8/"}]},{"title":"GPU的应用与发展","slug":"GPU的应用与发展","date":"2019-12-07T06:54:05.000Z","updated":"2024-10-11T18:29:55.293Z","comments":true,"path":"2019/12/07/GPU的应用与发展/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/07/GPU%E7%9A%84%E5%BA%94%E7%94%A8%E4%B8%8E%E5%8F%91%E5%B1%95/","excerpt":"","text":"1. GPU的起源  作为计算机中主要计算单元的中央处理器CPU(Central Process unit)经过了50多年的发展,已经具有很高的运算速度,CPU受工艺和功耗的约束时钟频率已经到达了极限,很难再有提高[1]。但随着图形图像处理技术的进一步发展,对于运算速度有了更高的要求,于是用于实现图形加速的GPU就随之产生了。图形处理器GPU(Graphic Processing Unit)的概念,最先由NVIDIA公司在1999年发布它的GeForce 256图形处理芯片时提出。一块标准的GPU芯片一般主要包括如下的部件:2D单元、3D单元、视频处理单元、FSAA(Full Scene Anti-aliasing全景抗锯齿)单元和显存管理单元[2]。 2. GPU与CPU的对比  作为从CPU之上发展起来的GPU,它们之间存在着差异。   首先是两者架构的不同。CPU一般由逻辑运算单元ALU,控制单元和存储单元组成。CPU有70%的晶体管用来构建Cache和一部分控制单元。CPU虽然有多核,但总数没有超过两位数,每个核都有足够大的缓存。CPU有足够多的数字和逻辑运算单元,并辅助有很多加速分支判断甚至更复杂的逻辑判断的硬件。所以CPU拥有超强的逻辑能力。GPU包括大量的运算单元,数字逻辑运算单元也少而简单。GPU的核数远超CPU,被称为众核,但每个核拥有的缓存大小相对小。于是GPU拥有超高的运算速度[3]。由于拥有多核,GPU的功耗远远超过CPU。   其次是应用场景。由于GPU的设计架构,GPU对于数据量大,类型不复杂,具有类似性,计算量大但是逻辑性不强的平行数据具有很强的并行运算能力[4]。而CPU是设计用来处理串行任务的处理、加工、运算以及系统核心控制等工作,CPU的架构是为高效率处理数据相关性不大的计算类、复杂繁琐的非计算类等工作而设计的。GPU相对于CPU拥有高带宽的独立显存、高浮点运算性能、几何计算能力强、适合处理并行计算任务的优势[4]。 3. GPU的应用  GPU最早的设计用途是用于作为CPU的辅助计算部件,用于加速图形图像计算,基本上是为了进行图形的消隐,纹理映射,图形的坐标位置变换、光照计算等。基于Intel提出Larrabee架构,NVIDIA提出的CUDA(Compute Unified Device Architecture统一计算设备架构)等[3]。使GPU变为一种可编程的显示计算通用架构芯片有了更多的用途。   现代的GPU应用于高性能计算[5],基于GPU的异构计算系统应用于超级计算机的研发。如我国的自主研发的千亿万级超级计算机“天河一号”使用的便是GPU+x64 CPU的基础架构。天河一号采用14336个英特尔六核处理器Xeon X5670 2.93GHz和2048颗拥有我国自主知识产权的x64八核处理器飞腾FT-100作为超算的主控处理器和调度节点。系统的协处理器采用了英伟达Tesla M2050 GPU。超级计算机“神威·太湖之光”也是基于GPU+CPU架构完成的[6]。  现代GPU也被用于人工智能与机器学习领域。人工智能和机器学习两者都是大规模并行问题,而这正是GPU所擅长的。意大利能源公司 Eni和美国的Stone Ridge科技使用3200个NVIDIA Tesla GPU和Stone Ridge的ECHELON软件进行基于GPU 的油矿仿真,在大约 15.5 小时内就能处理了10万个油矿模型,每一个模型平均在28分钟内就能仿真了油矿15年的生产量。如果使用传统的硬件和软件,完成这项任务需要10天的时间[7]。 4. GPU的发展历程  GPU技术发展的主要主要引导者是NVIDIA。NVIDIA占有着最大的市场份额[8]。到目前为止,NVIDIA公司一共正式发布了六款GPU 核心架构,分别是2008 年推出的Tesla 架构(首次发布了 CUDA 通用并行计算开发架构)、2010 年推出的Fermi架构(首次在GPU 中增加了FP64 双精度浮点运算处理核)、2012 年推出的Kepler架构(首次在GPU中引入了动态并行计算技术)、2014年推出的Maxwell架构(低 功耗优化及支持微软的DX12图形加速接口)、2016年推出的Pascal架构(优化统一内存寻址unified memory机制,首次引入3D内存及NVLink 高速互联总线)、2017年5月推出的Volta架构(首次引入Tensor(张量)运算单元)[9]。   GPU芯片如今主要的应用于高性能计算领域,基于GPU的异构计算系统应用于超级计算机的研发。应用于人工智能,机器学习技术,甚至还出现了专用于深度学习的GPU芯片[10]。未来的GPU芯片将朝着更小的体积,更快的运算速度,更低的成本,更广的应用范围,更高的集成度发展。   随着可编程的显示计算通用架构芯片的成熟,它将逐步取代GPU的地位,显卡则会慢慢被集成取代,作为独立硬件生存的空间会越来越小。正如英特尔公司高级副总裁兼数字企业事业部总经理帕特·基辛格在IDF 峰会上的讲话。“可编程的显示计算通用架构芯片是一场革命,它将颠覆持续了几十年的显卡产业,可编程的显示计算通用架构芯片虽然不会马上替代显卡,但是在三四年之后,随着我们相关技术、产品的成熟上市,显卡产业将消亡。”[2] 参考文献[1]李红辉,刘冬冬,杨芳南.CPU+GPU异构并行计算技术研究[J].信息系统工程,2018(05):39-40. [2] 周治国.GPU CPU谁革谁的命[J].上海信息化,2008,(6) [3]https://www.nvidia.cn/object/what-is-gpu-computing-cn.html [4]钟联波.GPU与CPU的比较分析[J].技术与市场,2009,16(09):13-14. [5]李红辉,刘冬冬,杨芳南.CPU+GPU异构并行计算技术研究[J].信息系统工程,2018(05):39-40. [6]邓彦伶.基于GPU的异构计算技术在超级计算领域的现状及发展展望[J].电脑迷,2017(08):201. [7]Andy Patrizio. GPU:为何对HPC和AI越来越重要?[N]. 计算机世界,2018-08-06(005). [8]曹璐云.AMD和NVIDIA两家公司GPU架构由分到合发展综述[J].信息与电脑(理论版),2016(11):58-59+99. [9]陈云海.Nvidia GPU核心架构技术演进分析[J].广东通信技术,2018(11):52-58+77. [10]寒意.以深度学习为目标,NVIDIA发布首款Volta架构GPU[J].华东科技,2017(05):14.","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"GPU","slug":"GPU","permalink":"https://tomsworkspace.github.io/tags/GPU/"},{"name":"CPU与GPU","slug":"CPU与GPU","permalink":"https://tomsworkspace.github.io/tags/CPU%E4%B8%8EGPU/"},{"name":"图形图像处理器","slug":"图形图像处理器","permalink":"https://tomsworkspace.github.io/tags/%E5%9B%BE%E5%BD%A2%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%E5%99%A8/"}]},{"title":"中国智能硬件调研报告","slug":"中国智能硬件调研报告","date":"2019-12-07T06:53:27.000Z","updated":"2024-10-11T18:29:55.316Z","comments":true,"path":"2019/12/07/中国智能硬件调研报告/","link":"","permalink":"https://tomsworkspace.github.io/2019/12/07/%E4%B8%AD%E5%9B%BD%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6%E8%B0%83%E7%A0%94%E6%8A%A5%E5%91%8A/","excerpt":"","text":"一、介绍  随着万物互联时代的到来,硬件智能化成为全社会的共识,在此背景下,智能硬件成为全球发展 最快、市场潜力最大的行业之一。由于政府、科技企业的高度重视和大力投入,智能硬件产业得到了快速发展,出货量、装机量和市场规模显著提升。   随着智能化、互联网化与社会经济、人民生活的结合日益紧密,以可穿戴设备、智能家居设备、机器人等为代表的智能硬件正在蓬勃兴起,掀起新一轮的终端产业变革。近几年,相关产品形态快速创新,向生产生活各领域渗透,并通过对传统设备的智能化改造,大大提升原有产业附加值、延伸产业链,加速推动新的生产组织模式变革。   根据市场研究机构数据,2015年我国智能硬件市场规模达到2750亿元,2016年达到3310亿元, 同比增长21%。2018年,我国智能硬件产业规模超过4700亿元,接近5000亿元,同比增速维持15%以上,产业发展格局初步形成,产业应用水平明显提升。智能硬件在大众消费市场逐步普及,在全球市场将具有较强国际竞争力。[1] 二、什么是智能硬件  智能硬件是继智能手机之后的一个科技概念,通过软硬件结合的方式,对传统设备进行改造,进而让其拥有智能化的功能。智能化之后,硬件具备连接的能力,实现互联网服务的加载,形成“云端”的典型架构,具备了大等附加价值。   智能硬件是一个科技概念,指通过将硬件和软件相结合对传统设备进行智能化改造。而智能硬件移动应用则是软件,通过应用连接智能硬件,操作简单,开发简便,各式应用层出不穷,也是企业获取用户的重要入口。   改造对象可能是电子设备,例如手表、电视和其他电器;也可能是以前没有电子化的设备,例如门锁、茶杯、汽车甚至房子。   智能硬件已经从可穿戴设备延伸到智能电视、智能家居、智能汽车、医疗健康、智能玩具、机器人等领域。比较典型的智能硬件包括Google Glass、三星Gear、FitBit、麦开水杯、咕咚手环、Tesla、乐视电视等。   智能硬件是以平台性底层软硬件为基础,以智能传感互联、人机交互、新型显示及大数据处理等新一代信息技术为特征,以新设计、新材料、新工艺硬件为载体的新型智能终端产品及服务。随着技术升级、关联基础设施完善和应用服务市场的不断成熟,智能硬件的产品形态从智能手机延伸到智能可穿戴、智能家居、智能车载、医疗健康、智能无人系统等,成为信息技术与传统产业融合的交汇点。[2] 三、典型产品简介  目前在智能硬件行业比较大的一些公司有谷歌,苹果,百度,奇虎360,JAWBONE NIKE,Microsoft,三星,华为,小米等。对于智能硬件应用领域大致有如下几种:智能家居,智能可穿戴设备,智能交通,智能医疗以及其他领域的智能硬件。 (一)智能家居  智能家居是在互联网影响之下物联化的体现。智能家居通过物联网技术将家中的各种设备(如音视频设备、照明系统、窗帘控制、空调控制、安防系统、数字影院系统、影音服务器、影柜系统、网络家电等)连接到一起,提供家电控制、照明控制、电话远程控制、室内外遥控、防盗报警、环境监测、暖通控制、红外转发以及可编程定时控制等多种功能和手段。与普通家居相比,智能家居不仅具有传统的居住功能,兼备建筑、网络通信、信息家电、设备自动化,提供全方位的信息交互功能,甚至为各种能源费用节约资金。[3]   比较典型的智能家居产品如百度近期发布的小度在家1S.   小度在家1S采用全球顶级设计与全新硬件配置,它由来自加州的Sonos智能音响工业设计师Wai-loong Lim操刀设计,造型典雅,自然融入家居,线条简洁,稳重大气;在音质方面,通过18项硬件升级和28项算法改进,为用户带来更澎湃的音效;相比小度在家,小度在家1S无线数据传输速度提升100%,视频通话更清晰、追剧听歌更流畅,家庭影音娱乐体验更上一层楼。   在内容资源上,小度在家1S通过与爱奇艺、QQ音乐、蜻蜓FM、斗鱼等多品牌合作,已拥有千万级音乐曲库,海量视频内容及优质的有声及儿童教育资源,不止于此,小度在家1S还最新引入了百度视频资源,并与喜马拉雅达成合作,接入喜马拉雅开放平台全量内容,涵盖喜马拉雅最优质的1亿+精品音频内容,为用户提供更丰富的声音内容收听体验。   值得一提的是,小度在家1S儿童模式全面升级,拥有行业最优儿童唤醒识别、儿童专属TTS音色和对话以及更适合儿童的交互界面,让孩子全程交互无障碍。儿童模式还专为孩子再增加三重保护:时长保护、距离保护和内容保护,不论是孩子离屏幕太近还是观看时间过长,小度都会及时提醒,同时会过滤掉不宜儿童视听的内容,呵护孩子的身心健康。 (二)智能可穿戴设备&emps; “穿戴式智能设备”是应用穿戴式技术对日常穿戴进行智能化设计、开发出可以穿戴的设备的总称,如眼镜、手套、手表、服饰及鞋等。广义穿戴式智能设备包括功能全、尺寸大、可不依赖智能手机实现完整或者部分的功能,例如:智能手表或智能眼镜等,以及只专注于某一类应用功能,需要和其它设备如智能手机配合使用,如各类进行体征监测的智能手环、智能首饰等。随着技术的进步以及用户需求的变迁,可穿戴式智能设备的形态与应用热点也在不断的变化。   典型的智能可穿戴设备如小米手环。   小米手环的主要功能包括查看运动量,监测睡眠质量,智能闹钟唤醒等。可以通过手机应用实时查看运动量,监测走路和跑步的效果,还可以通过云端识别更多的运动项目。   小米手环能够自动判断是否进入睡眠状态,分别记录深睡及浅睡并汇总睡眠时间,帮助用户监测自己的睡眠质量。   小米手环配备了低功耗蓝牙芯片及加速传感器,待机可达30天。另外,它支持IP67级别防水防尘,意味着日常生活,甚至是洗澡都无须摘下。 (三)智能交通  智能交通:智能交通是一个基于现代电子信息技术面向交通运输的服务系统。它的突出特点是以信息的收集、处理、发布、交换、分析、利用为主线,为交通参与者提供多样性的服务。   智能交通的发展跟物联网的发展是离不开的,只有物联网技术概念的不断发展,智能交通系统才能越来越完善。智能交通是交通的物联化体现。   21世纪将是公路交通智能化的世纪,人们将要采用的智能交通系统,是一种先进的一体化交通综合管理系统。在该系统中,车辆靠自己的智能在道路上自由行驶,公路靠自身的智能将交通流量调整至最佳状态,借助于这个系统,管理人员对道路、车辆的行踪将掌握得一清二楚。   如百度地图今年推出的智能停车系统。   该系统不仅可以有效地解决当地车主出行“停车难”的问题,而且也为银川的智能城市建设提供了助力,为其它城市的交通问题提供了参考。据悉,此次百度在银川推出的智能停车系统,基于多维停车大数据之上,然后进一步结合政府的实时监控,实现了停车场资源的规范化、智能化,甚至可以平均减少车主寻找车位10%的停车时间。同时该系统不仅可以方便驾驶员导航出行时的停车位,甚至还能在停车后准确的找到车辆的停放位置。   该系统可在一定程度上查询到目的地附近的停车位,以及了解其车位的使用情况,如果车位紧张,系统将自动提醒并及时提供替换方案。甚至驾驶员还能够使用停车助手,提前预约车位并在地图上一键发起导航,再也不用担心遇见停车位正好停满的情况。   同时在陌生的停车场停车,很容易发生找不到车辆停放位置的尴尬情况。如今在百度地图智能停车系统的帮助之下,驾驶员在抵达车位并一键降锁之后,系统将自动记录车辆停放位置。所以在反向寻车时,车主只需用百度地图一键发起寻车,即可在步行导航的指引下轻松找到车辆的停放位置。 (四)智能医疗  智能医疗是通过打造健康档案区域医疗信息平台,利用最先进的物联网技术,实现患者与医务人员、医疗机构、医疗设备之间的互动,逐步达到信息化。 在不久的将来医疗行业将融入更多人工智慧、传感技术等高科技,使医疗服务走向真正意义的智能化,推动医疗事业的繁荣发展。在中国新医改的大背景下,智能医疗正在走进寻常百姓的生活。   典型产品如结合了人工智能技术的达·芬奇手术机器人系统。   它主要分为两个部分:手术台和由医生远程操控的系统终端。在手术台设有一个拥有三个机械臂的机器人,由它负责进行手术,精准度和灵活性都比人类医生来得好,尤其适用于高难度的手术。而在系统终端,医生所操控的计算机拥有可拍摄二维图像的摄像机,并将人体内的情况利用三维图像清晰地显示出来,让医生可以监控整个手术过程。目前这款手术机器人已经协助完成了近300万例的手术。 四、特点  各大厂商纷纷在智能硬件领域长线布局,智能硬件产业飞速发展,主流产品如智能手环、智能 手表、智能插座、智能路由器、智能电视、智能家居与智能健康、智能驾驶汽车、消费级无人机等产品等纷纷涌现,智能硬件开始向大众市场渗透。智能硬件是一个快速发展的产业领域,新产品、 新技术、新应用、新模式层出不穷,产业边界在不断变化,并呈现以下主要特点[4]。   根据目前的产业状况,以产品的形态来看智能硬件包括可穿戴设备、智能家居、智能车载、智能无人系统、智慧健康以及其他面向消费和工业领域的智能化产品。智能硬件在本质上,是基于智能芯片、大规模集成电路、互联网及移动互联网、软件与应用、大数据和云计算、人工智能和虚拟现实、新能源和新材料以及生物技术等前沿技术领域的发展,跨界融合而形成的全新的产品形态。   目前,发达国家均认可智能硬件是抢占未来信息化制高点的重要的途径和战略举措。在国际上, 以苹果、谷歌、微软、Facebook、特斯拉、高通等为代表的国际巨头,投入大量资源进行智能硬件技术研发和市场培育;在国内,百度、阿里、腾讯、小米、京东、乐视、360、华为、联想等企业也投入巨资构建自己的产业生态体系,快速推动智能硬件产业的发展。当前,全球正处于万物互联时代,物联网社会已经到来。随着前沿技术发展、企业和资本的广泛参与,智能硬件正快速的拓展到经济与社会的各个领域,释放和催生工业、农业、服务业、节能 环保、商贸流通、交通能源、公共安全、社会事业、城市管理等各个领域的新需求。智能硬件通过产生全新的生产制造和商业应用模式,激发大众创业、万众创新活力,大幅提高产业发展的质量和效率,为经济结构调整和产业转型升级创造了全新的发展空间,形成新的经济增长极。   伴随各大领域企业竞相构建智能硬件生态,行业发展已从单品之争转向平台之争,平台的重要 性日益凸显。在智能硬件底层软硬件技术公共服务平台方面,国内外直接或间接竞争对手有苹果智能家居平台HomeKit和车载信息平台CarPlay、三星智能家居平台SmartHome、Google智能家居系统Brillo和车载信息平台Android Auto、百度智能健康设备平台dulife、小米生活服务平台 MIUI 6、阿里巴巴物联网平台、腾讯“QQ 物联”智能硬件开放平台、杰升科技机智云等。 五、发展现状(一)智能硬件产品形成一定的比较优势  可穿戴设备发展较快,企业保持全球前列。从全球市场看,据IDC最新数据显示,2016年第三季度全球可穿戴设备出货量排名中,小米公司占据第二,市场份额约为16.5%。我国企业步步高凭借儿童智能手表等产品,出货量也处于全球领先地位。从国内市场看,2016年第二季度中国可穿戴设备市场出货量为954万台,同比增长81.4%。中国可穿戴设备市场的高速增长主要依靠本土厂商在细分市场的精耕细作,及其综合实力的快速成长,这主要体现在4个方面:产品由硬向软的转变;功能由小到大的成长;渠道由点向面的覆盖;市场由内向外的扩张。智能家居稳步增长,应用标准和平台逐步形成。由于智能电视渗透率即将步入瓶颈,增长速度逐步放缓,而小家电及其他智能家居产品还未进入爆发期,因此国内智能家居市场总体仍将保持增长,但是增长速度在缓慢下降。家居平台和应用标准逐步形成,我国企业海尔、阿里巴巴、百度、小米等均已建立不同类型 的智能家居应用平台,如海尔的平台具备较完善的软 件和硬件开发API标准、大数据平台、开放的SDK等。 此外,互联网企业在布局智能家居时都在寻求适合的 跨界模式,与传统家居跨界合作频繁,以求相互取长补短增强竞争力。 我国无人机企业一家独大,带动形成成熟的区域产业链。我国企业大疆科技占据全球无人机市场约70%的份额,其中80%销量出口国外。核心技术方面,大疆自主研发的无人机飞控系统和航拍设备的性能业界领先,除自用外,还为下游制造企业提供飞控系统、任务设备和动力设备,加强产业主导能力的大疆的快速发展带动珠三角地区形成完整的无人机产业链,涵盖机架、电池、电机、云台、飞控系统、整机集成、投资、供应链服务、爱好者社区等关键节点,并辐射北京、上海。但如今Intel、高通、3DRobotic等国际企业布局加 快,加强了在消费级无人机芯片、飞控技术上的研发,同时“效仿”安卓的产业生态策略,以基础技术开源为手段加快对产业链下游的控制,我国无人机产业面临开源技术生态竞争,存在控制力下降的风险。 (二)核心技术环节仍然薄弱  MCU与短距离芯片仍处于追赶阶段,低功率广域网芯片抢占先机。在MCU方面,中国本土的MCU供应商在2016年稳步增长,代表企业如中颍、Gigadevice 和晟矽微都保持14%的年增长速度,但国产MCU主要仍以低端4/8位单片机为主,大部分仍用于消费电子、 家电、水表等专用市场。在短距离通信芯片方面,2015年国内Wi-Fi芯片迅速增长,国内大多厂商均成立于2000年以后,以中小型企业为主,成熟应用产品仍较少。在NB-IoT方面,我国相关技术发展较早,并利用先发优势,计划推出相应芯片,如华为海思作为 NB-Io主导方,计划推出NB-IoT商用芯片。新型传感器缺乏布局,产品性能与国外差距明显。我国传感器总体水平较国外差距较大,对于新型传感器缺乏布局。运动传感器普及度较高,ST、博世等国际大厂技术领先(性能稳定)、规模大(ST累计90亿 颗)。而国内传感器品类单一、规模小,技术水平总体较低。如格科威和豪威CMOS传感器出货量占据全球一半的市场份额,但是市场规模合计只有15%,充分体现我国CMOS传感器主要面向低端市场,缺少高端产品。此外,在光线、温湿度、距离、心率传感器等趋势性 应用领域基本空白。AMOLED产业为韩国三星垄断,柔性显示发展较慢。在AMOLED方面,小尺寸AMOLED面板三星的市占率逾95%,为打破垄断现状,国内厂商在此领域纷纷布局,京东方(2013年年底)、和辉光电(2014年年底)成功量产,华星光电、国显光电、天马、信利等企业也积极建设AMOLED生产线。在柔性显示方面,三星 显示器主宰了市场,国内企业仍处于追赶状态,产业链相对不够成熟,相关材料及设备均要依赖进口,人才和技术仍是弱项。 六、未来发展趋势(一)随着国家战略性扶持力度的加大,智能硬件的发展将呈现出不断增长的趋势[5]。  在中央及地方政策不断完善。国家出台一系列政策,成立联盟或产业基地,发力支持智能硬件产业的发展。包括国务院《关于积极推进“互联网+”行动的指导 意见》、发改委《“互联网+”人工智能3年行动实施方 案》、工信部《新兴智能硬件产业创新发展专项行动》、北京成立中关村智能硬件联盟、上海成立“科技50”智能硬件产业基地等。此外,产业相关标准正在完善,包括《智能硬件白皮书》、《中国可穿戴联盟标准》等。 (二)智能机器人的使用将会是未来发展的重头戏。  现在智能产品普及率高,人们享受到了科技带给人们的便利。但是,相对于智能手表,智能手环等智能产品而言,智能机器人的使用还不是很广泛,普及性不高。国外科学家们正在努力开发智能机器人,使之成为未来智能硬件产业的一个“开拓者”。在未来可能会面临一种情况,如:繁重的家务活、精细的手术、有危险的技术操作等一些体力劳动工作,都可以依靠机器人来完成,甚至一些脑力活动也能被机器人所取代。在未来,智能机器人的使用也将会实现爆炸式的增长。相关数据显示,在 2014 年,美国、德国等国家每10个工业人员,就会使用1台以上工业机器人。全球工业已经进入以大数据、云计算、智能机器人和 3D 打印技术等为典型代表的“第四次工 业革命”[6]。在未来,智能机器人将在工业领域大展身手。 七、参考文献[1]赛迪智库. 智能硬件产业发展白皮书[N]. 中国计算机报,2018-12-24(008). [2]2019-2023年中国智能硬件行业深度调研及投资前景预测报告.[3]李天祥编. ANDROID物联网开发细致入门与最佳实践[M]. 2016 第14页. [4]AVC.2016上半年智能硬件行业发展总结与趋势展望[R]. AVC,2016. [5]易观智库.中国创新智能硬件市场发展专题研究报告[R]. 易观智库,2015. [6]李碧生.把握智能制造机遇 推动工业转型升级——西安工业经济智能制造革命浅析[J].经济研究导刊,2018,(19):46-47.","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"智能硬件","slug":"智能硬件","permalink":"https://tomsworkspace.github.io/tags/%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6/"}]},{"title":"Linux-Arm page table","slug":"Linux-Arm-page-table","date":"2019-11-26T08:44:04.000Z","updated":"2024-10-11T18:29:55.298Z","comments":true,"path":"2019/11/26/Linux-Arm-page-table/","link":"","permalink":"https://tomsworkspace.github.io/2019/11/26/Linux-Arm-page-table/","excerpt":"","text":"一.多级页表的概念  介绍页表之前,我们先来回顾一个操作系统里的基本概念,虚拟内存。 1.1 虚拟内存  在用户的视角里,每个进程都有自己独立的地址空间,A进程的4GB和B进程4GB是完全独立不相关的,他们看到的都是操作系统虚拟出来的地址空间。但是呢,虚拟地址最终还是要落在实际内存的物理地址上进行操作的。操作系统就会通过页表的机制来实现进程的虚拟地址到物理地址的翻译工作。其中每一页的大小都是固定的。页表管理有两个关键点,分别是页面大小和页表级数。 1.2 页面大小  在Linux下,我们通过如下命令可以查看到当前操作系统的页大小 # getconf PAGE_SIZE 1.3 页表级数  页表级数越少,虚拟地址到物理地址的映射会很快,但是需要管理的页表项会很多,能支持的地址空间也有限。相反页表级数越多,需要的存储的页表数据就会越少,而且能支持到比较大的地址空间,但是虚拟地址到物理地址的映射就会越慢。   举个例子。如果想支持32位的操作系统下的4GB进程虚拟地址空间,假设页表大小为4K,则共有2的20次方页面。如果采用速度最快的1级页表,对应则需要2的20次方个页表项。一个页表项假如4字节,那么一个进程就需要(10485764=4M)的内存来存页表项。如果是采用2级页表,则只需要页目录1024个,页表项1024个,总共2028个页表管理条目,(20484=)8k就可以支持起4GB的地址空间转换。更何况操作系统需要支持的可是64位地址空间,而且要支持成百上千的进程,这个开销会大道不可忍。所以每个操作系统制定页表级数的时候都是在映射速度和页表占用空间中取折中。   Linux在v2.6.11以后,最终采用的方案是4级页表,分别是: PGD:page Global directory(47-39), 页全局目录 PUD:Page Upper Directory(38-30),页上级目录 PMD:page middle directory(29-21),页中间目录 PTE:page table entry(20-12),页表项   这样,一个64位的虚拟空间,就需要:2^9 个PGD + 2^9 个PUD + 2^9 个PMD + 2^9 个PTE = 2048个页表数据结构。现在的页表数据结构被扩展到了8byte。仅仅需要(2048*8=)16K就可以支持起(2^48 =)256T的进程地址空间。 PGD: Page Global Directory   Linux系统中每个进程对应用户空间的pgd是不一样的,但是linux内核的pgd是一样的。当创建一个新的进程时,都要为新进程创建一个新的页面目录PGD,并从内核的页面目录swapper_pg_dir中复制内核区间页面目录项至新建进程页面目录PGD的相应位置,具体过程如下do_fork()->copy_mm()->mm_init()->pgd_alloc()->set_pgd_fast()->get_pgd_slow()->memcpy(&PGD+USER_PTRS_PER_PGD,swapper_pg_dir+USER_PTRS_PER_PGD,(PTRS_PER_PGD-USER_PTRS_PER_PGD)*sizeof(pgd_t))这样一来,每个进程的页面目录就分成了两部分,第一部分为“用户空间”,用来映射其整个进程空间(0x0000 0000-0xBFFF FFFF)即3G字节的虚拟地址;第二部分为“系统空间”,用来映射(0xC000 0000-0xFFFF FFFF)1G字节的虚拟地址。可以看出Linux系统中每个进程的页面目录的第二部分是相同的,所以从进程的角度来看,每个进程有4G字节的虚拟空间,较低的3G字节是自己的用户空间,最高的1G字节则为与所有进程以及内核共享的系统空间。每个进程有它自己的PGD( Page Global Directory),它是一个物理页,并包含一个pgd_t数组。 PTE: 页表项(page table entry)   PGD中包含若干PUD的地址,PUD中包含若干PMD的地址,PMD中又包含若干PT的地址。每一个PTE页表项指向一个页框,也就是一个实际的物理页面。 1.4 页表带来的问题  虽然16K的页表数据支持起了256T的地址空间寻址。但是,这也带来了额外的问题,页表是存在内存里的。那就是一次内存IO光是虚拟地址到物理地址的转换就要去内存查4次页表,再算上真正的内存访问,竟然需要5次内存IO才能获取一个内存数据! TLB应运而生   和CPU的L1、L2、L3的缓存思想一致,既然进行地址转换需要的内存IO次数多,且耗时。那么干脆就在CPU里把页表尽可能地cache起来不就行了么,所以就有了TLB(Translation Lookaside Buffer),专门用于改进虚拟地址到物理地址转换速度的缓存。其访问速度非常快,和寄存器相当,比L1访问还快。有了TLB之后,CPU访问某个虚拟内存地址的过程如下: 1.CPU产生一个虚拟地址 2.MMU从TLB中获取页表,翻译成物理地址 3.MMU把物理地址发送给L1/L2/L3/内存 4.L1/L2/L3/内存将地址对应数据返回给CPU   由于第2步是类似于寄存器的访问速度,所以如果TLB能命中,则虚拟地址到物理地址的时间开销几乎可以忽略。 二.内核页表和进程页表  内核页表:即主内核页表,在内核中其实就是一段内存,存放在主内核页全局目录init_mm.pgd(swapper_pg_dir)中,硬件并不直接使用。   进程页表:每个进程自己的页表,放在进程自身的页目录task_struct.pgd中。   在保护模式下,从硬件角度看,其运行的基本对象为“进程”(或线程),而寻址则依赖于“进程页表”,在进程调度而进行上下文切换时,会进行页表的切换:即将新进程的pgd(页目录)加载到CR3寄存器中。   1、 内核页表中的内容为所有进程共享,每个进程都有自己的“进程页表”,“进程页表中映射的线性地址包括两部分:用户态,内核态。   其中,内核态地址对应的相关页表项,对于所有进程来说都是相同的(因为内核空间对所有进程来说都是共享的),而这部分页表内容其实就来源于“内核页表”,即每个进程的“进程页表”中内核态地址相关的页表项都是“内核页表”的一个拷贝。   2、“内核页表”由内核自己维护并更新,在vmalloc区发生page fault时,将“内核页表”同步到“进程页表”中。以32位系统为例,内核页表主要包含两部分:线性映射区,vmalloc区。   其中,线性映射区即通过TASK_SIZE偏移进行映射的区域,对32系统来说就是0-896M这部分区域,映射对应的虚拟地址区域为TASK_SIZE-TASK_SIZE+896M。这部分区域在内核初始化时就已经完成映射,并创建好相应的页表,即这部分虚拟内存区域不会发生page fault。   vmalloc区,为896M-896M+128M,这部分区域用于映射高端内存,有三种映射方式:vmalloc、固定、临时。以vmalloc为例(最常使用),这部分区域对应的线性地址在内核使用vmalloc分配内存时,其实就已经分配了相应的物理内存,并做了相应的映射,建立了相应的页表项,但相关页表项仅写入了“内核页表”,并没有实时更新到“进程页表中”,内核在这里使用了“延迟更新”的策略,将“进程页表”真正更新推迟到第一次访问相关线性地址,发生page fault时,此时在page fault的处理流程中进行“进程页表”的更新。 2.1 主内核页表 swapper_pg_dir  主内核页表负责维护内核空间的页表映射,完成后的内核空间主页表映射如下图 (每一项映射1M空间,lk项刚好映射4G虚拟空间)swapper_pg_dir用于存放内核PGD页表的地方,赋给内核页表init_mm.pgd。swapper_pd_dir的大小为16KB,对应的虚拟地址空间是从0xc0004000 - 0xc0008000,物理地址空间是0x6000400~0x60008000。swapper_pg_dir被定义了绝对地址,在arch/arm/kernel/head.S中定义。 2.2 用户进程页表2.2.1 进程如何使用内存  毫无疑问,所有进程(执行的程序)都必须占用一定数量的内存,它或是用来存放从磁盘载入的程序代码,或是存放取自用户输入的数据等等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。对任何一个普通进程来讲,它都会涉及到5种不同的数据段。   代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。   数据段:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。   BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。   堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)   栈:栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。 2.2.2 进程页表如何创建进程的内核页全局目录的装载过程   do_fork()->copy_process()->copy_mm()(如果是fork一个内核线程kernel thread的话,内核线程将会直接使用当前普通进程的页表集,内核线程并不拥有自己的页表集)->dup_mm()->mm_init()->mm_alloc_pgd()->pgd_alloc   pgd_ctor(mm, pgd) //将swapper_pg_dir全局页目录(部分后256项–即内核最后1G的虚拟地址,这里指的是内核的页表)拷到pgd里,则可以看出,linux下所有进程的内核页全局目录是一样的,都是swapper_pg_dir里最后的1/4的内容,而每个进程的用户态的页表确是不同的,所以在dup_mmap会去将父进程的页表一项一项的爬出来设置为当前进程的页表。 进程的用户态地址页拷贝   dup_mmap()函数实现页表映射的拷贝   页表的复制 copy_page_range() copy_pud_range() copy_pmd_range() copy_pte_range()   cr3寄存器的加载   cr3寄存器的加载是在进程调度的时候更新的,具体如下schedule()->context_switch()->switch_mm()->load_cr3(next->pgd)。load_cr3加载的是mm_struct->pgd,即线性地址,而实际上加裁到cr3寄存器的是实际的物理地址write_cr3(__pa(pgdir));在装载cr3寄存器时将线性地址通过__pa转换成了物理地址了,所以cr3寄存器是装的是实实在在的物理地址。 2.2.3 用户进程页如何分配   bss段:BSS段属于静态内存分配。通常是指用来存放程序中未初始化的全局变量和未初始化的局部静态变量。未初始化的全局变量和未初始化的局部静态变量默认值是0,本来这些变量也可以放到data段的,但是因为他们都是0,所以为他们在data段分配空间并且存放数据0是没有必要的。程序在运行时,才会给BSS段里面的变量分配内存空间。在目标文件(*.o)和可执行文件中,BSS段只是为未初始化的全局变量和未初始化的局部静态变量预留位置而已,它并没有内容,所以它不占据空间。section table中保存了BSS段(未初始化的全局变量和未初始化的局部静态变量)内存空间大小总和。(objdump -h *.o 命令可以看到)   data段:数据段(datasegment)通常是指用来存放程序中已初始化的全局变量和已初始化的静态变量的一块内存区域。数据段属于静态内存分配。   text段:代码段(codesegment/textsegment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。   rodata段:存放的是只读数据,比如字符串常量,全局const变量 和 #define定义的常量。   heap堆:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)   stack栈:是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。   创建进程,虚拟地址和物理地址之间的映射关系   上面的图说明:同一个变量,地址相同,其实是虚拟地址相同,内容不同其实是被映射到了不同的物理地址!   过程:当访问虚拟内存时,会访问MMU(内存管理单元)去匹配对应的物理地址,而如果虚拟内存的页并不存在于物理内存中,会产生缺页中断,从磁盘中取得缺的页放入内存,如果内存已满,还会根据某种算法将磁盘中的页换出。(MMU中存储页表,用来匹配虚拟内存和物理内存)","categories":[{"name":"Linux kernel","slug":"Linux-kernel","permalink":"https://tomsworkspace.github.io/categories/Linux-kernel/"}],"tags":[{"name":"linux-Arm","slug":"linux-Arm","permalink":"https://tomsworkspace.github.io/tags/linux-Arm/"},{"name":"page table","slug":"page-table","permalink":"https://tomsworkspace.github.io/tags/page-table/"},{"name":"页表","slug":"页表","permalink":"https://tomsworkspace.github.io/tags/%E9%A1%B5%E8%A1%A8/"},{"name":"PGD PTE","slug":"PGD-PTE","permalink":"https://tomsworkspace.github.io/tags/PGD-PTE/"},{"name":"四级页表","slug":"四级页表","permalink":"https://tomsworkspace.github.io/tags/%E5%9B%9B%E7%BA%A7%E9%A1%B5%E8%A1%A8/"}]},{"title":"DLL-lose","slug":"DLL-lose","date":"2019-11-21T07:55:47.000Z","updated":"2024-10-11T18:29:55.289Z","comments":true,"path":"2019/11/21/DLL-lose/","link":"","permalink":"https://tomsworkspace.github.io/2019/11/21/DLL-lose/","excerpt":"  在安装某些软件,我们正准备开开心心地打开,哦豁,duang的一声弹出一个框框。就像下面这样。 这时候是不是一筹莫展呢?别灰心,这类问题大多数还是能解决的。","text":"  在安装某些软件,我们正准备开开心心地打开,哦豁,duang的一声弹出一个框框。就像下面这样。 这时候是不是一筹莫展呢?别灰心,这类问题大多数还是能解决的。 1. DLL文件的概念1.1 什么是dll文件  DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。   在 Windows操作系统中,每个程序都可以使用该 DLL 中包含的功能来实现“打开”对话框。   DLL文件中存放的是各类程序的函数(子过程)实现过程,当程序需要调用函数时需要先载入DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是程序不需要在运行之初加载所有代码,只有在程序需要某个函数的时候才从DLL中取出。这有助于促进代码重用和内存的有效使用。 1.2 使用dll文件的好处 实现模块化   通过使用 DLL,程序可以实现模块化,由相对独立的组件组成。例如,一个记账程序可以按模块来销售。可以在运行时将各个模块加载到主程序中(如果安装了相应模块)。因为模块是彼此独立的,所以程序的加载速度更快,而且模块只在相应的功能被请求时才加载。另外,使用DLL文件还可以减小程序的体积。 便于应用更新   可以更为容易地将更新应用于各个模块,而不会影响该程序的其他部分。例如,您可能具有一个工资计算程序,而税率每年都会更改。当这些更改被隔离到 DLL 中以后,您无需重新生成或安装整个程序就可以应用更新。 2. dll文件丢失的解决办法  当某个程序或 DLL 使用其他 DLL 中的 DLL 函数时,就会创建依赖项。因此,该程序就不再是独立的,并且如果该依赖项被损坏,该程序就可能遇到问题。就会产生类似于.dll文件丢失这样的问题。 2.1 dll文件丢失的原因  出现DLL文件丢失一般出现在Windows系统中。产生dll文件丢失的原因有很多。大概总结了一下,有以下的几种: (1)程序依赖的 DLL文件升级到新版本 (2)未安装程序需要的DLL文件 (3)依赖 DLL 被其早期版本覆盖 (4)从计算机中删除了依赖 DLL (5)由dll文件命名引发的丢失 2.2 解决办法  解决dll丢失问题的方法有一下几种,不过并不是所有的解决方法都能解决问题。在选择解决问题的方法之前先找到产生丢失dll的具体原因是什么,还有丢失的dll文件是什么类型的。然后再对症下药,方能药到病除。 丢失文件的类型:   丢失的dll文件是与编程语言和系统环境有关的dll文件。一般出现在microsoft自己的软件运行时出现,比如许多微软自己开发的开发工具,VS ,vc++,Qt之类的程序,可能的原因是(1)(2)(3)(4)。   丢失的dll文件是与具体程序相关的,非microsoft相关的,一般出现在一个刚安装的程序或者不需要安装可以直接运行的exe文件运行时出现的。还有就是网上下载的所谓的破解版的软件最容易出现这种问题。出现的原因可能是(2)(4)。 解决方法: 方法1:下载一键式修复工具   有许多人开发了专门针对这类dll丢失问题的一键式修复工具。如 Diretx工具   这是一个一键批量检测当前系统丢失的dll文件并进行自动修复,使用方法是最简单的。只能解决第一类问题中的少部分问题,可以用来修复那些系统相关的dll文件。使用方法 方法2:下载丢失的对应的DLL文件并放到对应的目录   将dll文件复制到Windows系统目录或者复制到程序安装目录中。针对报丢失的dll文件,按照名字去搜索对应的DLL文件下载,并放置在对应的目录。一般第一类的问题,和系统相关的dll文件放在系统对应的目录下。(32位系统在 C:\\Windows\\System32,64位系统放在C:\\Windows\\SysWOW64下)和程序相关的放在对应程序安装目录下。一般是这样,但是也不是绝对的,也有的程序丢失的dll放在系统目录下的,比如有的.exe程序。   下面给出一些可以搜索下载dll文件的网址: Dll-files https://www.zhaodll.com/ Dll之家 脚本之家 DLL文件下载器或者https://gitee.com/wyatthuang/dll_downloader   这个是一个爬取工具。原理是通过Python的urllib库,爬取DLL共享网站https://cn.dll-files.com, 并下载dll文件。软件运行后,按照提示提示搜索下载就可以了,非常很简单。 方法3:在对应的目录检查一下文件命名的问题   这个问题一般不会出现,一般由于自己下载的dll文件已经放在对应目录下但是由于命名的原因没有识别到。 方法4:重新安装程序   重新执行出现问题程序的安装程序,重新安装来解决dll丢失问题。不过对于系统dll文件丢失和.exe程序没有作用。 方法5:使用Windows系统文件检查器修复.dll错误(sfc / scannow) https://www.reneelab.com.cn/windows-10-sfc-scannow.html 方法6:重启大法   在使用多种方法不起效或者使用重装大法之前,可以使用重启系统试试。重启可以让没启用的配置生效,或许可以解决你的问题。 方法7:通过重装或者更新Windows操作系统来摆脱dll错误   这个方法成本太大了,不建议使用 一般来说,大多数问题通过这些方法都是可以解决的。如果还有的话,请留言告诉我一声。哈哈","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"Dll文件丢失","slug":"Dll文件丢失","permalink":"https://tomsworkspace.github.io/tags/Dll%E6%96%87%E4%BB%B6%E4%B8%A2%E5%A4%B1/"}]},{"title":"CPU Cache","slug":"CPU-Cache","date":"2019-11-17T12:20:30.000Z","updated":"2024-10-11T18:29:55.270Z","comments":true,"path":"2019/11/17/CPU-Cache/","link":"","permalink":"https://tomsworkspace.github.io/2019/11/17/CPU-Cache/","excerpt":"","text":"Linux的CPU cache一. CPU 与 Memory 内存之间的三级缓存的实现原理1.1 cache 存在的原理  引入 Cache 的理论基础是程序局部性原理,包括时间局部性和空间局部性。时间局部性原理即最近被CPU访问的数据,短期内CPU 还要访问(时间);空间局部性即被CPU访问的数据附近的数据,CPU短期内还要访问(空间)。因此如果将刚刚访问过的数据缓存在一个速度比主存快得多的存储中,那下次访问时,可以直接从这个存储中取,其速度可以得到数量级的提高。   CPU缓存是(Cache Memory)位于CPU与内存之间的临时存储器,它的容量比内存小但交换速度快。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。   在CPU中加入缓存是一种高效的解决方案,是对于存储器件成本更低,速度更快这两个互相矛盾的目标的一个最优解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统了。缓存对CPU的性能影响很大,主要是因为CPU的数据交换顺序和CPU与缓存间的带宽引起的。下图是一个典型的存储器层次结构,我们可以看到一共使用了三级缓存 各级存储访问延迟的对比 1.2 cpu 三级cache  介于CPU和主存储器间的高速小容量存储器,由静态存储芯片SRAM组成,容量较小但比主存DRAM技术更加昂贵而快速, 接近于CPU的速度。CPU往往需要重复读取同样的数据块, Cache的引入与缓存容量的增大,可以大幅提升CPU内部读取数据的命中率,从而提高系统性能。通常由高速存储器、联想存储器、地址转换部件、替换部件等组成。如图所示。 联想存储器:根据内容进行寻址的存储器(冯氏模型中是按照地址进行寻址,但在高速存储器中往往只存有部分信息,此时需要根据内容进行检索) 地址转换部件:通过联想存储器建立目录表以实现快速地址转换。命中时直接访问Cache;未命中时从内存读取放入Cache 替换部件:在缓存已满时按一定策略进行数据块替换,并修改地址转换部件   早期采用外部(Off-chip)Cache,不做在CPU内而是独立设置一个Cache。现在采用片内(On-chip)Cache,将Cache和CPU作在一个芯片上,且采用多级Cache,同时使用L1 Cache和L2 Cache,甚至有L3 Cache。 一般L1 Cache都是分立Cache,分为数据缓存和指令缓存,可以减少访存冲突引起的结构冒险,这样多条指令可以并行执行;内置;其成本最高,对CPU的性能影响最大多级Cache的情况下,L1 Cache的命中时间比命中率更重要 一般L2 Cache都是联合Cache,这样空间利用率高没有L3 Cache的情况下,L2 Cache的命中率比命中时间更重要(缺失时需从主存取数,并要送L1和L2 cache) L3 Cache多为外置,在游戏和服务器领域有效;但对很多应用来说,总线改善比设置L3更加有利于提升系统性能   上图显示了最简单的缓存配置。它对应着最早期使用CPU cache的系统的架构。CPU内核不再直接连接到主内存。所有的数据加载和存储都必须经过缓存。CPU核心与缓存之间的连接是一种特殊的快速连接。在一个简化的表示中,主存和高速缓存连接到系统总线,该系统总线也可用于与系统的其他组件进行通信。我们引入了系统总线(现代叫做“FSB”)。引入缓存后不久,系统变得更加复杂。高速缓存和主存之间的速度差异再次增大,使得另一个级别的高速缓存不得不被添加进来,它比第一级高速缓存更大且更慢。出于经济原因,仅增加第一级缓存的大小不是一种选择。今天,甚至有机器在生产环境中使用了三级缓存。带有这种处理器的系统如图下所示。随着单个CPU的内核数量的增加,未来的缓存级别数量可能会增加。现在已经出现了拥有四级cache的处理器了。   上图展示了三级缓存的结构。L1d是一级数据cache,L1i是一级指令cache。请注意,这只是一个示意图; 现实中的数据流从core到主存的过程中不需要经过任何更高级别的cache。CPU设计人员有很大的自由来设计cache的接口。对于程序员来说,这些设计选择是不可见的。另外,我们有拥有多个core的处理器,每个core可以有多个“线程”。核心和线程之间的区别在于,独立的核心具有所有硬件资源的独立的副本,早期的多核处理器,甚至具有单独的第二级缓存而没有第三级缓存。核心可以完全独立运行,除非它们在同一时间使用相同的资源,例如与外部的连接。另一方面,线程们共享几乎所有的处理器资源。英特尔的线程实现只为线程提供单独的寄存器,甚至是有限的,还有一些寄存器是共享的。一个现代CPU的完整概貌如图所示。 i-cache和d-cache都是32KB、8路、4个时钟周期; L2 cache:256KB 、8路、11个时钟周期。 所有核共享的L3 cache:8MB、16路、30~40个时钟周期。 Core i7中所有cache的块大小都是64B 1.3 cpu cache与TLB的联系  由于cache中对应的都是主存地址,即物理地址,在cqu查看具体数据是否在cache中时,如果CPU传送过来的地址时一个虚拟地址,需要将其转换成实际物理地址再到cache中去寻找。Cache的实现需要TLB的帮助。可以说TLB命中是Cache命中的基本条件。TLB不命中,会更新TLB项,这个代价非常大,Cache命中的好处基本都没有了。在TLB命中的情况下,物理地址才能够被选出,Cache的命中与否才能够达成。 1.3.1 TLB的概述  TLB是一个内存管理单元用于改进虚拟地址到物理地址转换速度的缓存。TLB是位于内存中的页表的cache,如果没有TLB,则每次取数据都需要两次访问内存,即查页表获得物理地址和取数据。 1.3.2 TLB的原理  当cpu对数据进行读请求时,CPU根据虚拟地址(前20位)到TLB中查找.TLB中保存着虚拟地址(前20位)和页框号的对映关系,如果匹配到虚拟地址就可以迅速找到页框号(页框号可以理解为页表项),通过页框号与虚拟地址后12位的偏移组合得到最终的物理地址.   如果没在TLB中匹配到虚拟地址,就出现TLB丢失,需要到页表中查询页表项,如果不在页表中,说明要读取的内容不在内存,需要到磁盘读取.   TLB是MMU中的一块高速缓存,也是一种Cache.在分页机制中,TLB中的数据和页表的数据关联,不是由处理器维护,而是由OS来维护,TLB的刷新是通过装入处理器中的CR3寄存器来完成.如果MMU发现在TLB中没有命中,它在常规的页表查找后,用找到的页表项替换TLB中的一个条目. 1.3.3 TLB的刷新原则  当进程进行上下文切换时重新设置cr3寄存器,并且刷新tlb.有两种情况可以避免刷tlb. 第一种情况是使用相同页表的进程切换. 第二种情况是普通进程切换到内核线程.lazy-tlb(懒惰模式)的技术是为了避免进程切换导致tlb被刷新.当普通进程切换到内核线程时,系统进入lazy-tlb模式,切到普通进程时退出该模式. 1.3.4 cache的概念  cache是为了解决处理器与慢速DRAM(慢速DRAM即内存)设备之间巨大的速度差异而出现的。cache属于硬件系统,linux不能管理cache.但会提供flush整个cache的接口.cache分为一级cache,二级cache,三级cache等等.一级cache与cpu处于同一个指令周期. 1.3.5 Cache的存取单位(Cache Line)  CPU从来不从DRAM直接读/写字节或字,从CPU到DRAM的每次读或写的第一步都要经过L1 cache,每次以整数行读或写到DRAM中.Cache Line是cache与DRAM同步的最小单位.典型的虚拟内存页面大小为4KB,而典型的Cache line通常的大小为32或64字节.   CPU 读/写内存都要通过Cache,如果数据不在Cache中,需要把数据以Cache Line为单位去填充到Cache,即使是读/写一个字节.CPU 不存在直接读/写内存的情况,每次读/写内存都要经过Cache. 1.3.6 Cache的工作模式 数据回写(write-back):这是最高性能的模式,也是最典型的,在回写模式下,cache内容更改不需要每次都写回内存,直到一个新的 cache要刷新或软件要求刷新时,才写回内存. 写通过(write-through):这种模式比回写模式效率低,因为它每次强制将内容写回内存,以额外地保存cache的结果,在这种模式写耗时,而读和回写模一样快,这都为了内存与cache相一致而付出的代价. 预取 (prefectching):一些cache允许处理器对cache line进行预取,以响应读请求,这样被读取的相邻内容也同时被读出来,如果读是随机的,将会使CPU变慢,预取一般与软件进行配合以达到最高性能. 二. 各级缓存中数据的包含关系2.1 整个缓存和主存间  缓存里有的数据,主存中一定存在。 2.2 各级缓存之间  一级缓存中还分数据缓存(data cache,d-cache)和指令缓存(instruction cache,i-cache)。二者分别用来存放数据和执行这些数据的指令,而且两者可以同时被cpu访问,所以一级cache间数据时独立的。   一级没有的数据二级可能有也可能没有。因为一级缓存miss会接着访问二级缓存。   一级有二级一定有,三级也一定有。因为一级的数据从二级中读上来的。在一级缺失二级命中时发生。   二级没有的数据三级可能有也可能没有。因为二级确实会接着访问三级缓存。找不到会继续访问主存。   二级有的数据三级一定有。在二级缺失三级命中时,数据会从三级缓存提到二级缓存。   三级没有的数据,主存可能有也可能没有。三级缓存缺失,会访问主存,主存也缺失就要从外存访问数据了。   三级缓存有的数据主存一定有。因为在三级缺失主存命中时,数据会从主存提到三级缓存中来。 三. 各级缓存的大小设置  一级缓存就是指CPU第一层级的高速缓存,主要是为了缓存指令和缓存数据,一级缓存的容量对CPU性能影响非常大,但是因为成本太高,所以一般容量特别小,也就256KB左右。   二级缓存是CPU第二层级的高速缓存,对于CPU来说,二级缓存容量越大越好,它是直接影响CPU性能的,CPU每个核心都会有自己的缓存,一个CPU的二级缓存容量是所有核心二级缓存容量的总和。   三级缓存就是CPU第三层级的高速缓存,主要是为了降低与内存进行数据传输时的延迟问题,三级缓存与一二级不同,三级缓存只有一个,它是所有核心共享,所以在CPU参数中可以看到,三级缓存相对于其他两级缓存来说都很大。   由于缓存的设置与OS无关且透明,所以对于不同的体系架构下不同的处理器对待缓存区域的处理和方式都不同,不同的处理器也有不同的缓存设置值。从主流的处理器cache大小来看,一般一个cache line的大小都是固定的64B左右,这是经过经验得到的比较合理的大小,一般一级cache大小在数十KB左右,二级cache大小在数十到数百KB左右,而L3 cache大小在数MB左右。 四. 各级缓存之间的数据放置与数据淘汰策略  由于三级cache一般来说是运用于拥有多核的处理器,对于单核处理器来说二级cache就能够足够保持够高的cache命中率。所以一般的三级cache一般只针对于多核处理器。L1和L2级cache是处理器核所单独的内容。L1又可以看成是L2的cache。L2可以看成是L3级cache的cache。所以我们分两个部分讨论数据放置与数据淘汰策略。 4.1 各级cache之间数据放置方式  各级cache间的数据放置策略主要有三种。直接相连映射,全相联映射和组相联映射。将一个主存块存储到唯一的一个Cache行。对应的大小都是一个cache line的大小,一般来说是64B。 4.1.1直接相连映射  多对一的映射关系,但一个主存块只能拷贝到cache的一个特定行位置上去。cache的行号i和主存的块号j有如下函数关系:i=j mod m(m为cache中的总行数)。 优点:硬件简单,容易实现 缺点:命中率低, Cache的存储空间利用率低 4.1.2全相联映射  可以将一个主存块存储到任意一个Cache行。主存的一个块直接拷贝到cache中的任意一行上 优点:命中率较高,Cache的存储空间利用率高 缺点:线路复杂,成本高,速度低 4.1.3组相联映射  可以将一个主存块存储到唯一的一个Cache组中任意一个行。将cache分成u组,每组v行,主存块存放到哪个组是固定的,至于存到该组哪一行是灵活的,即有如下函数关系:cache总行数m=u×v 组号q=j mod u   组间采用直接映射,组内为全相联。硬件较简单,速度较快,命中率较高。是现代处理器中一般所常用的映射方式。 4.2 数据淘汰策略  Cache工作原理要求它尽量保存最新数据,当从主存向Cache传送一个新块,而Cache中可用位置已被占满时,就会产生Cache替换的问题。常用的替换算法有下面三种。 4.2.1 LFU  LFU(Least Frequently Used,最不经常使用)算法将一段时间内被访问次数最少的那个块替换出去。每块设置一个计数器,从0开始计数,每访问一次,被访块的计数器就增1。当需要替换时,将计数值最小的块换出,同时将所有块的计数器都清零。这种算法将计数周期限定在对这些特定块两次替换之间的间隔时间内,不能严格反映近期访问情况,新调入的块很容易被替换出去。 4.2.2 LRU  LRU(Least Recently Used,近期最少使用)算法是把CPU近期最少使用的块替换出去。这种替换方法需要随时记录Cache中各块的使用情况,以便确定哪个块是近期最少使用的块。每块也设置一个计数器,Cache每命中一次,命中块计数器清零,其他各块计数器增1。当需要替换时,将计数值最大的块换出。  LRU算法相对合理,但实现起来比较复杂,系统开销较大。这种算法保护了刚调入Cache的新数据块,具有较高的命中率。LRU算法不能肯定调出去的块近期不会再被使用,所以这种替换算法不能算作最合理、最优秀的算法。但是研究表明,采用这种算法可使Cache的命中率达到90%左右。 4.2.3 随机替换  最简单的替换算法是随机替换。随机替换算法完全不管Cache的情况,简单地根据一个随机数选择一块替换出去。随机替换算法在硬件上容易实现,且速度也比前两种算法快。缺点则是降低了命中率和Cache工作效率。 五. 整个缓存结构的访问流程5.1 查找命中  处理器微架构访问Cache的方法与访问主存储器有类似之处。主存储器使用地址编码方式,微架构可以地址寻址方式访问这些存储器。Cache也使用了类似的地址编码方式,微架构也是使用这些地址操纵着各级Cache,可以将数据写入Cache,也可以从Cache中读出内容。只是这一切微架构针对Cache的操作并不是简单的地址访问操作。为简化起见,我们忽略各类Virtual Cache,讨论最基础的Cache访问操作,并借此讨论CPU如何使用TLB完成虚实地址转换,最终完成对Cache的读写操作。   Cache的存在使得CPU Core的存储器读写操作略微显得复杂。CPU Core在进行存储器方式时,首先使用EPN(Effective Page Number)进行虚实地址转换,并同时使用CLN(Cache Line Number)查找合适的Cache Block。这两个步骤可以同时进行。在使用Virtual Cache时,还可以使用虚拟地址对Cache进行寻址。为简化起见,我们并不考虑Virtual Cache的实现细节。   EPN经过转换后得到VPN,之后在TLB中查找并得到最终的RPN(Real Page Number)。如果期间发生了TLB Miss,将带来一系列的严重的系统惩罚,我们只讨论TLB Hit的情况,此时将很快获得合适的RPN,并依此得到PA(Physical Address)。   在多数处理器微架构中,Cache由多行多列组成,使用CLN进行索引最终可以得到一个完整的Cache Block。但是在这个Cache Block中的数据并不一定是CPU Core所需要的。因此有必要进行一些检查,将Cache Block中存放的Address与通过虚实地址转换得到的PA进行地址比较(Compare Address)。如果结果相同而且状态位匹配,则表明Cache Hit。此时微架构再经过Byte Select and Align部件最终获得所需要的数据。如果发生Cache Miss,CPU需要使用PA进一步索引主存储器获得最终的数据。   由上文的分析,我们可以发现,一个Cache Block由预先存放的地址信息,状态位和数据单元组成。一个Cache由多个这样的Cache Block组成,在不同的微架构中,可以使用不同的Cache Block组成结构。我们首先分析单个Cache Block的组成结构。单个Cache Block由Tag字段,状态位和数据单元组成,如图所示。   其中Data字段存放该Cache Block中的数据,在多数处理器微架构中,其大小为32或者64字节。Status字段存放当前Cache Block的状态,在多数处理器系统中,这个状态字段包含MESI,MOESI或者MESIF这些状态信息,在有些微架构的Cache Block中,还存在一个L位,表示当前Cache Block是否可以锁定。许多将Cache模拟成SRAM的微架构就是利用了这个L位。有关MOESIFL这些状态位的说明将在下文中详细描述。在多核处理器和复杂的Cache Hierarchy环境下,状态信息远不止MOESIF。   RAT(Real Address Tag)记录在该Cache Block中存放的Data字段与那个地址相关,在RAT中存放的是部分物理地址信息,虽然在一个CPU中物理地址可能有40,46或者48位,但是在Cache中并不需要存放全部地址信息。因为从Cache的角度上看,CPU使用的地址被分解成为了若干段,如图所示。   这个地址也可以理解为CPU访问Cache使用的地址,由多个数据段组成。首先需要说明的是Cache Line Index字段。这一字段与Cache Line Number类似,CPU使用该字段从Cache中选择一个或者一组Entry。   Bank和Byte字段之和确定了单个Cache的Data字段长度,通常也将这个长度称为Cache 行长度,上图所示的微架构中的Cache Block长度为64字节。目前多数支持DDR3 SDRAM的微架构使用的Cache Block长度都是64字节。部分原因是由于DDR3 SDRAM的一次Burst Line为8,一次基本Burst操作访问的数据大小为64字节。   在处理器微架构中,将地址为Bank和Byte两个字段出于提高Cache Block访问效率的考虑。Multi-Bank Mechanism是一种常用的提高访问效率的方法,采用这种机制后,CPU访问Cache时,只要不是对同一个Bank进行访问,即可并发执行。Byte字段决定了Cache的端口位宽,在现代微架构中,访问Cache的总线位宽为64位或者为128位。   剩余的字段即为Real Address Tag,这个字段与单个Cache中的Real Address Tag的字段长度相同。CPU使用地址中的Real Address Tag字段与Cache Block的对应字段和一些状态位进行联合比较,判断其访问数据是否在Cache中命中 5.2 cache 不命中  如果cache miss,就去下一级cache或者主存中去查找数据,并将查找到的数据采用上面的数据淘汰策略将数据替换到cache中。 5.3 cache和主存数据一致性保持  由于在发生cache miss时会产生数据替换,在运行过程中缓存的数据也可能会被修改。所以需要一个策略来保持数据在缓存和主存间的一致性。Cache写机制分为write through和write back两种。 Write-through(直写模式)在数据更新时,同时写入缓存Cache和主存存储。此模式的优点是操作简单;缺点是因为数据修改需要同时写入存储,数据写入速度较慢。 Write-back(回写模式)在数据更新时只写入缓存Cache。只在数据被替换出缓存时,被修改的缓存数据才会被写到后端存储。此模式的优点是数据写入速度快,因为不需要写存储;缺点是一旦更新后的数据未被写入存储时出现系统掉电的情况,数据将无法找回。","categories":[{"name":"Linux kernel","slug":"Linux-kernel","permalink":"https://tomsworkspace.github.io/categories/Linux-kernel/"}],"tags":[{"name":"cache","slug":"cache","permalink":"https://tomsworkspace.github.io/tags/cache/"},{"name":"CPU cache","slug":"CPU-cache","permalink":"https://tomsworkspace.github.io/tags/CPU-cache/"},{"name":"三级缓存","slug":"三级缓存","permalink":"https://tomsworkspace.github.io/tags/%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98/"},{"name":"缓存映射","slug":"缓存映射","permalink":"https://tomsworkspace.github.io/tags/%E7%BC%93%E5%AD%98%E6%98%A0%E5%B0%84/"},{"name":"cache原理","slug":"cache原理","permalink":"https://tomsworkspace.github.io/tags/cache%E5%8E%9F%E7%90%86/"},{"name":"多级cache","slug":"多级cache","permalink":"https://tomsworkspace.github.io/tags/%E5%A4%9A%E7%BA%A7cache/"},{"name":"TLB","slug":"TLB","permalink":"https://tomsworkspace.github.io/tags/TLB/"}]},{"title":"搭建个人博客","slug":"搭建个人博客","date":"2019-11-13T08:36:37.000Z","updated":"2024-10-11T18:29:55.335Z","comments":true,"path":"2019/11/13/搭建个人博客/","link":"","permalink":"https://tomsworkspace.github.io/2019/11/13/%E6%90%AD%E5%BB%BA%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/","excerpt":"对于常年需要学习新东西的人们,经常想把学过的一些东西整理一下。于是写博客就成了很多人整理自己学习过的东西的很好的方式。有人选择在一些成熟的平台上管理自己的博客,比如知乎,CSDN,简书这些平台。不过在别人的平台上写东西很容易受到各种限制,内容也有各种各样的要求。哪有比自己搭一个私人博客网站更炫酷(装*)的呢!!!! 哈哈,let’s go.","text":"对于常年需要学习新东西的人们,经常想把学过的一些东西整理一下。于是写博客就成了很多人整理自己学习过的东西的很好的方式。有人选择在一些成熟的平台上管理自己的博客,比如知乎,CSDN,简书这些平台。不过在别人的平台上写东西很容易受到各种限制,内容也有各种各样的要求。哪有比自己搭一个私人博客网站更炫酷(装*)的呢!!!! 哈哈,let’s go. 一.效果展示经过特别简单的配置(大概几个小时),一个属于个人的博客网站就搭好了,可以开始更新自己的内容了,毕竟内容才是最重要的啊。 效果如下: 二.前期准备经过总结前人的经验,我把搭建一个个人的博客分为大概三种可行的方案: 第一种方案:搭建一个通过Github pages访问的博客网站。但是这样的话只能通过访问 http://github用户名.github.io的方式访问自己的博客网站,毕竟不够炫酷,但是是免费的。于是有了第二种方案。 第二种方案:把 http://github用户名.github.io绑定到自己的域名,购买一个域名需要一定的费用。 第三种方案:把博客系统放到自己购买服务器上,这样能通过服务器的IP地址访问博客网站,绑定域名后可以通过域名来访问。这是最炫酷的方式,当然也是pay最多的,而且网站还需要经过备案。 由于自己热爱open sourse(其实是没钱),于是我选择了第一种的方案。希望后面有钱了可以实现第三种方案。哈哈。 2.1 选择一个博客框架为了避免重复造轮子,也为了简单和高效起见,我们需要选择一个已有的博客框架,再在这些框架的基础上搭建。有很多还不错的开源博客框架可以选择。简单列举几个: - Jekyll - hugo - django - hexo 经过比较,发现hexo框架还不错,有比较好的扩展性和很大的用户基础。于是Tom就选了hexo作为博客的框架。想选择其他框架的请参照具体官网文档,和他们的GitHub issues。 2.2 hexo框架的简介Hexo 特点 支持Markdown: 支持Markdown意味着你可以把经历从排版中解放出来 轻量: 无需拥有后台及数据库,专心写好你的文章 一键部署: 可以通过Git或者ftp来将生成的静态页面部署到服务器或者主机空间中 插件丰富: 丰富的插件可以满足你的各种需求 其他特性,请参考官方文档. 2.3 配置必要的环境搭建前期需要四个工具 Hexo:Hexo快速、简洁且高效的博客框架 Node.js:建立在Chrome上的JavaScript运行引擎 Git:一款免费、开源的分布式版本控制系统 GitHub:国内一款面向开发者的云端开发平台,提供代码托管,运行空间,质量控制,项目管理等功能安装 安装必要的配置环境,如果已经安装过,可以跳过。 2.3.1 安装Git Git官网找到Download,安装自己对应的系统版本,系统会自动判定你当前版本,推荐下载,如果没有推荐,那就自己选择吧。 安装完后,用win+R打开cmd界面,输入 $git --version 出现git的版本信息说明安装成功。 然后熟悉一下Git管理Github项目的常用命令,理解一下他们的关系,感受一下git版本和分支管理的强大之处。 2.3.2 安装Node.js Node.js下载地址安装node.js记得选择add to path选项,或者手动配置环境变量。把node所在的bin目录加入环境变量。用win+R打开cmd界面,输入 $node 出现node.js的版本信息说明安装成功。 同理系统依然会判定你的系统并给出稳定推荐的版本和尝鲜版,两种,供君选择。下载安装步骤同样省略。 2.3.3 GitHub账号注册GitHub账号注册过程很简单,注册流程就省略,完成之后,就开始创建博客了。注册完成后,创建名字为 username.github.io 的仓库,username是你的github用户名。记住一定是这个名字的仓库,不然会出问题的。 2.3.4 安装Hexo前面的所有工具安装完成后,在git bash或者cmd界面中使用npm安装hexo。 $npm install -g hexo-cli 进阶安装和使用,对于熟悉 npm 的进阶用户,可以仅局部安装 hexo 包。 $npm install hexo 安装 Hexo 完成后,请执行下列命令查看安装hexo的版本信息。 $hexo version 然后运行以下命令,Hexo 将会在指定文件夹中新建所需要的文件。这个指定的文件夹就是保存你的博客站点所有文件的地方。 $hexo init <folder>$cd <folder> $npm install 站点文件包含以下的文件 . ├── _config.yml //网站的配置文件 ├── package.json //应用程序的信息 ├── scaffolds //模版 文件夹。当您新建文章时,Hexo 会根据 scaffold 来建立文件。 ├── source //资源文件夹是存放用户资源的地方。除 _posts 文件夹之外,开头命名为 _ (下划线)的文件 / 文件夹和隐藏的文件将会被忽略。Markdown 和 HTML 文件会被解析并放到 public 文件夹,而其他文件会被拷贝过去。 | ├── _drafts | └── _posts └── themes //主题 文件夹。Hexo 会根据主题来生成静态页面。 更多关于站点目录和配置文件的信息。 安装git,hexo,node.js后,熟悉相关命令。不同系统环境下安装遇到的问题请参考官方文档,官方文档支持中英文,很方便。 三.hexo框架搭建博客3.1 建立hexo项目,本地localhost:port访问在上一步生成了站点文件夹后,其中会有一个默认主题以及一个hello-word的默认文章。所以我们可以先生成博客来看一下效果,在站点所在目录打开git bash运行命令: $hexo generate //可以简写成$hexo g 然后hexo会开始生成博客,生成结束后,文件夹下会多出一个public的文件夹,这个文件夹就是hexo生成的一个完整的静态网站,也就是我们的博客。网站生成好了,我们要浏览它,所以要开启一下hexo自带的服务器,运行命令: $hexo server //可以简写成$hexo s 这时候,打开浏览器,输入localhost:4000即可访问博客网站。如果出现端口冲突,可以使用如下的命令指定其他端口。再使用localhost:port去访问。 $hexo server -p port //可以简写成$hexo g -p port 3.2 远程部署,绑定SSH,域名访问远程部署指的是,博客在我们本地生成好了以后部署到远程仓库去,如果远程仓库支持pages服务的话,那就可以通过这样的方法发布和更新博客。要使用远程部署需要先安装hexo-deployer-git,注意,这是适用于git类型仓库的方法,其他仓库的方法在官网中有说明。 运行命令 $npm install hexo-deployer-git --save //安装hexo-deployer-git package 安装好hexo-deployer-git后,修改博客目录配置文件_config.yml中的deploy字段,用记事本打开就可以: deploy: type: git //pages 部署仓库类型 repo: //git仓库项目地址,你建立的 用户名.github.io仓库 branch: //分支,默认是master message: //自定义提交说明,这个字段可以没有,用于描述你的提交 再运行 $hexo deploy 打开github的repo,发现你的仓库里已经有了文件,这是生成的博客网站的静态文件。也就是本地博客文件夹中的public 或者.deploy_git中的内容。hexo生成了你的博客网站的静态文件。在浏览器地址栏输入http://username.github.io就可以访问你的个人博客网站了。很炫酷吧。 参考的一些资料 https://www.cnblogs.com/liuxianan/p/build-blog-website-by-hexo-github.htmlhttps://blog.csdn.net/gsl9d1p04f6x2ts3c9/article/details/81024330https://blog.csdn.net/dazhaoDai/article/details/73730069https://www.jianshu.com/p/343934573342 绑定SSH Git使用https协议,每次pull, push都要输入密码,相当的烦。使用git协议,然后使用ssh密钥。这样可以省去每次都输密码。为了方便你的博客的提交,你可以进行git与repo仓库的SSH绑定。 SSH绑定git仓库的特点: ssh方式单独使用非对称的秘钥进行认证和加密传输,和账号密码分离开来,不需要账号也可以访问repo。 生成和管理秘钥有点繁琐,需要管理员添加成员的public key。不能进行匿名访问,ssh不利于对权限进行细分,用户必须具有通过SSH协议访问你主机的权限,才能进行下一步操作,比较适合内部项目。 如何进行SSH配置。 四.更换主题4.1 给你的博客选择一个主题经过以上的配置,你的博客网站已经跑起来了。不过是不是有点丑呢?看来需要进一步的美化我们的网站啊。当然Hexo,也大力支持我们的想法,Hexo提供了很多漂亮的主题供我们选择。Hexo主题。是不是发现有很多主题让你眼花缭乱了,如果不知道应该选哪一个,可以听一听来自前人的建议主题推荐1 主题推荐2。 4.2 更改相关配置找到自己喜欢的主题,把对应的主题下载到本地站点文件夹下的themes文件夹下。Tom选择了一个名为Butterfly的主题。想要让这个主题应用到我自己的站点上。需要进行如下的操作。 下载主题 找到下载主题的Github repo地址,在站点文件夹下右键打开git bash,输入命令 $git clone -b master https://github.com/jerryc127/hexo-theme-butterfly.git themes/Butterfly 如果想要安装比较新的dev分支,可以 $git clone -b dev https://github.com/jerryc127/hexo-theme-butterfly.git themes/Butterfly 应用主题 修改hexo配置文件_config.yml,把主题改为Butterfly theme: Butterfly 对于不同的主题,可能还有其他的配置才能生效,参加你选择主题的相关文档说明,关于主题Butterfly配置。 如果你没有pug以及stylus的渲染器,请下载安装: $npm install hexo-renderer-pug hexo-renderer-stylus --save 或者 $yarn add hexo-renderer-pug hexo-renderer-stylus 然后再次generate ,depoly,访问地址,主题就更换好了。 疑难问题解决 五.项目管理5.1 写博客,删除博客首先,由于hexo是支持Markdown来编写你的博客的,非常方便。markdown语法也是非常简单的,类似于html,很快就能掌握。在开始写你的博客之前需要先掌握一下。 但是在实际使用时发现了一个问题。在不同的平台对于markdown语法的支持不太一样,但是大多是东西是一样的,对于博客在不同平台间的迁移不太方便。暂时没有太好的解决办法,等有比较好的解决方法时更新一下。hexo支持的markdown 语法和github的markdown语法是一样的。然后最好是选择一个对于hexo markdown语法支持得比较好的makedown编辑器。 其他的markdown语法资料 https://segmentfault.com/markdown https://www.jianshu.com/p/8c1b2b39deb0 5.2 上传更新博客先在本地预览一下写好的博客,在站点文件夹下使用git bash运行 $hexo generate //hexo g$hexo server //hexo s 检查没问题后,提交更新 $hexo deploy //hexo d 六.高级功能6.1 评论管理如何给我们的评论添加评论功能呢? 以Tom选择的Butterfly主题为例,打开主题的配置文件夹_config.yml,注意不是站点的配置文件。搜索Comments,发现该主题支持如下的几种评论系统 disqus laibili gitalk valine 可以任选一种评论系统进行配置。大致流程为先注册对应的评论系统,再进行一些配置。主题Butterfly相关配置参见主题butterfly相关文档。 其他的配置方法。 6.2 绑定域名是不是感觉使用githubusername.github.io访问自己的博客还是不够炫酷呢?于是可以给自己的网站绑定一个炫酷的域名,也就是相当于给你的网站起了一个别名,本质上都是通过DNS查找ip地址,然后通过IP地址访问的你的网站的。 可以在CMD界面下通过命令来查看你博客的ip地址。 $ping username.github.io //ping 你的博客网址 首先注册一个域名,在阿里云上注册或者买一个域名。打开控制台,点击解析域名,把你刚刚查到的ip地址填到刚刚解析的域名的记录值上面。然后在你的博客文件夹下面新建一个名为CNAME的文件,在里面写入你刚刚购买的域名。然后提交你的站点静态文件到仓库里。等待一段时间,然后就可以通过刚刚购买的域名访问你的网站了。 6.3 部署站点到自己的服务器如果你觉得上面的配置还不过炫酷,速度不够快,毕竟github是国外的网站,那么还有更厉害的,把网站放到自己的服务器上。 网站备案 首先,需要进行网站备案。根据工信部《互联网信息服务管理办法》(国务院 292 号令),网站在未完成备案之前,不能指向大陆境内服务器开通访问。如果您的网站托管在中国大陆节点服务器,或者开通 CDN 服务,就必须申请 ICP(互联网内容提供商)备案。若网站服务器为非中国大陆节点,则不用申请备案。 然后购买一个服务器,把你的域名绑定到你的服务器上。 在你的服务器上安装Git并配置,安装并搭建web容器,如nginx,Tomcat等。 然后服务器获取站点静态文件有两种方式: 通过把站点静态文件直接提交到服务器上的git仓库 通过从github上的username.github.io获取站点静态文件 这样就完成了最后的配置。 6.4 更换电脑或重装系统后恢复hexo当我们的系统崩溃或者是其他原因导致hexo不能用了,试试这个恢复。 6.5 配置一键部署:每次更新博客都需要进到博客站点下进行操作,还有一堆的命令要输入。配置一键部署。 6.6 自定义什么? 你觉得所有的主题都不符合你的要求。没问题,hexo也支持你自己DIY主题,还可以发布给大家一起用。还支持自定义博客模板等功能。 6.7 博客迁移你想把自己博客发布到不同的平台上,或者是从其他平台上添加自己的博客。没问题,hexo也支持。试试博客迁移。 6.8 搜索引擎收录博客当我们一开始建完博客时,搜索引擎是搜索不到的,我们需要做一项工作就是通知搜索引擎收录我们的网站。 具体方法 6.9 jsDelivr免费CDN加速博客由于github的服务器是国外的服务器,访问速度相对较慢,特别是你的博客中使用了太多的图片等比较大的资源,这时候可以使用CDN加速给网站很快的访问速度。 具体方法 七.出现的一些问题git ssh配置Host key verification failed.YAMLException: can not read a block mapping entry; a multiline key may not be an implicit key at line 5, column bad indentation of a mapping entry at line 199, column 2:hexo YAMLException: cannot read a block mapping entry; a multi line key may not be an implicit key agitalk 出现not found 等问题深坑,申请gitalk账号一般不会出问题,一般问题出在配置_config.yml上。注意repo是只写仓库名字,不是全部路径啊。如username.github.io这样。 butterfly主题问题解答更多问题解答","categories":[{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"}],"tags":[{"name":"TOM","slug":"TOM","permalink":"https://tomsworkspace.github.io/tags/TOM/"},{"name":"个人博客","slug":"个人博客","permalink":"https://tomsworkspace.github.io/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"name":"personal blog","slug":"personal-blog","permalink":"https://tomsworkspace.github.io/tags/personal-blog/"},{"name":"Hexo","slug":"Hexo","permalink":"https://tomsworkspace.github.io/tags/Hexo/"},{"name":"Github pages","slug":"Github-pages","permalink":"https://tomsworkspace.github.io/tags/Github-pages/"}]}],"categories":[{"name":"OpenSourceSummer2021","slug":"OpenSourceSummer2021","permalink":"https://tomsworkspace.github.io/categories/OpenSourceSummer2021/"},{"name":"Computer Graphics","slug":"Computer-Graphics","permalink":"https://tomsworkspace.github.io/categories/Computer-Graphics/"},{"name":"Physics engine","slug":"Physics-engine","permalink":"https://tomsworkspace.github.io/categories/Physics-engine/"},{"name":"OpenGL","slug":"OpenGL","permalink":"https://tomsworkspace.github.io/categories/OpenGL/"},{"name":"文档开发","slug":"文档开发","permalink":"https://tomsworkspace.github.io/categories/%E6%96%87%E6%A1%A3%E5%BC%80%E5%8F%91/"},{"name":"CMAKE","slug":"CMAKE","permalink":"https://tomsworkspace.github.io/categories/CMAKE/"},{"name":"C/C++","slug":"C-C","permalink":"https://tomsworkspace.github.io/categories/C-C/"},{"name":"GCC","slug":"GCC","permalink":"https://tomsworkspace.github.io/categories/GCC/"},{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/categories/gem5/"},{"name":"其他","slug":"其他","permalink":"https://tomsworkspace.github.io/categories/%E5%85%B6%E4%BB%96/"},{"name":"Computer Architecture","slug":"Computer-Architecture","permalink":"https://tomsworkspace.github.io/categories/Computer-Architecture/"},{"name":"Linux kernel","slug":"Linux-kernel","permalink":"https://tomsworkspace.github.io/categories/Linux-kernel/"}],"tags":[{"name":"流体模拟","slug":"流体模拟","permalink":"https://tomsworkspace.github.io/tags/%E6%B5%81%E4%BD%93%E6%A8%A1%E6%8B%9F/"},{"name":"SPH方法","slug":"SPH方法","permalink":"https://tomsworkspace.github.io/tags/SPH%E6%96%B9%E6%B3%95/"},{"name":"拉格朗日视角与欧拉视角","slug":"拉格朗日视角与欧拉视角","permalink":"https://tomsworkspace.github.io/tags/%E6%8B%89%E6%A0%BC%E6%9C%97%E6%97%A5%E8%A7%86%E8%A7%92%E4%B8%8E%E6%AC%A7%E6%8B%89%E8%A7%86%E8%A7%92/"},{"name":"超弹性物体","slug":"超弹性物体","permalink":"https://tomsworkspace.github.io/tags/%E8%B6%85%E5%BC%B9%E6%80%A7%E7%89%A9%E4%BD%93/"},{"name":"有限元方法","slug":"有限元方法","permalink":"https://tomsworkspace.github.io/tags/%E6%9C%89%E9%99%90%E5%85%83%E6%96%B9%E6%B3%95/"},{"name":"FEM","slug":"FEM","permalink":"https://tomsworkspace.github.io/tags/FEM/"},{"name":"弹簧质点系统","slug":"弹簧质点系统","permalink":"https://tomsworkspace.github.io/tags/%E5%BC%B9%E7%B0%A7%E8%B4%A8%E7%82%B9%E7%B3%BB%E7%BB%9F/"},{"name":"时间积分","slug":"时间积分","permalink":"https://tomsworkspace.github.io/tags/%E6%97%B6%E9%97%B4%E7%A7%AF%E5%88%86/"},{"name":"OpenGL","slug":"OpenGL","permalink":"https://tomsworkspace.github.io/tags/OpenGL/"},{"name":"Doxygen","slug":"Doxygen","permalink":"https://tomsworkspace.github.io/tags/Doxygen/"},{"name":"CMAKE","slug":"CMAKE","permalink":"https://tomsworkspace.github.io/tags/CMAKE/"},{"name":"C++","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"参数传递","slug":"参数传递","permalink":"https://tomsworkspace.github.io/tags/%E5%8F%82%E6%95%B0%E4%BC%A0%E9%80%92/"},{"name":"指针","slug":"指针","permalink":"https://tomsworkspace.github.io/tags/%E6%8C%87%E9%92%88/"},{"name":"引用","slug":"引用","permalink":"https://tomsworkspace.github.io/tags/%E5%BC%95%E7%94%A8/"},{"name":"GCC","slug":"GCC","permalink":"https://tomsworkspace.github.io/tags/GCC/"},{"name":"builtin函数","slug":"builtin函数","permalink":"https://tomsworkspace.github.io/tags/builtin%E5%87%BD%E6%95%B0/"},{"name":"Const","slug":"Const","permalink":"https://tomsworkspace.github.io/tags/Const/"},{"name":"C 关键字 保留标识符","slug":"C-关键字-保留标识符","permalink":"https://tomsworkspace.github.io/tags/C-%E5%85%B3%E9%94%AE%E5%AD%97-%E4%BF%9D%E7%95%99%E6%A0%87%E8%AF%86%E7%AC%A6/"},{"name":"GEM5","slug":"GEM5","permalink":"https://tomsworkspace.github.io/tags/GEM5/"},{"name":"CPU2017","slug":"CPU2017","permalink":"https://tomsworkspace.github.io/tags/CPU2017/"},{"name":"多继承","slug":"多继承","permalink":"https://tomsworkspace.github.io/tags/%E5%A4%9A%E7%BB%A7%E6%89%BF/"},{"name":"虚函数表结构","slug":"虚函数表结构","permalink":"https://tomsworkspace.github.io/tags/%E8%99%9A%E5%87%BD%E6%95%B0%E8%A1%A8%E7%BB%93%E6%9E%84/"},{"name":"gem5","slug":"gem5","permalink":"https://tomsworkspace.github.io/tags/gem5/"},{"name":"benchmark","slug":"benchmark","permalink":"https://tomsworkspace.github.io/tags/benchmark/"},{"name":"trace","slug":"trace","permalink":"https://tomsworkspace.github.io/tags/trace/"},{"name":"MemoryAccess","slug":"MemoryAccess","permalink":"https://tomsworkspace.github.io/tags/MemoryAccess/"},{"name":"CCPU2006","slug":"CCPU2006","permalink":"https://tomsworkspace.github.io/tags/CCPU2006/"},{"name":"C","slug":"C","permalink":"https://tomsworkspace.github.io/tags/C/"},{"name":"C 标准","slug":"C-标准","permalink":"https://tomsworkspace.github.io/tags/C-%E6%A0%87%E5%87%86/"},{"name":"C C++ 区别","slug":"C-C-区别","permalink":"https://tomsworkspace.github.io/tags/C-C-%E5%8C%BA%E5%88%AB/"},{"name":"C11 关键字 _Static_assert","slug":"C11-关键字-Static-assert","permalink":"https://tomsworkspace.github.io/tags/C11-%E5%85%B3%E9%94%AE%E5%AD%97-Static-assert/"},{"name":"C 指针 多维数组指针","slug":"C-指针-多维数组指针","permalink":"https://tomsworkspace.github.io/tags/C-%E6%8C%87%E9%92%88-%E5%A4%9A%E7%BB%B4%E6%95%B0%E7%BB%84%E6%8C%87%E9%92%88/"},{"name":"C 存储类别 作用域 链接 存储期","slug":"C-存储类别-作用域-链接-存储期","permalink":"https://tomsworkspace.github.io/tags/C-%E5%AD%98%E5%82%A8%E7%B1%BB%E5%88%AB-%E4%BD%9C%E7%94%A8%E5%9F%9F-%E9%93%BE%E6%8E%A5-%E5%AD%98%E5%82%A8%E6%9C%9F/"},{"name":"C 缓冲区","slug":"C-缓冲区","permalink":"https://tomsworkspace.github.io/tags/C-%E7%BC%93%E5%86%B2%E5%8C%BA/"},{"name":"printf scanf 输入输出 C语言 格式控制","slug":"printf-scanf-输入输出-C语言-格式控制","permalink":"https://tomsworkspace.github.io/tags/printf-scanf-%E8%BE%93%E5%85%A5%E8%BE%93%E5%87%BA-C%E8%AF%AD%E8%A8%80-%E6%A0%BC%E5%BC%8F%E6%8E%A7%E5%88%B6/"},{"name":"免费CDN服务","slug":"免费CDN服务","permalink":"https://tomsworkspace.github.io/tags/%E5%85%8D%E8%B4%B9CDN%E6%9C%8D%E5%8A%A1/"},{"name":"加速个人博客","slug":"加速个人博客","permalink":"https://tomsworkspace.github.io/tags/%E5%8A%A0%E9%80%9F%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"name":"CDN","slug":"CDN","permalink":"https://tomsworkspace.github.io/tags/CDN/"},{"name":"github","slug":"github","permalink":"https://tomsworkspace.github.io/tags/github/"},{"name":"jsdelivr","slug":"jsdelivr","permalink":"https://tomsworkspace.github.io/tags/jsdelivr/"},{"name":"计算代码时钟周期数","slug":"计算代码时钟周期数","permalink":"https://tomsworkspace.github.io/tags/%E8%AE%A1%E7%AE%97%E4%BB%A3%E7%A0%81%E6%97%B6%E9%92%9F%E5%91%A8%E6%9C%9F%E6%95%B0/"},{"name":"Huffman编码","slug":"Huffman编码","permalink":"https://tomsworkspace.github.io/tags/Huffman%E7%BC%96%E7%A0%81/"},{"name":"文件解压缩","slug":"文件解压缩","permalink":"https://tomsworkspace.github.io/tags/%E6%96%87%E4%BB%B6%E8%A7%A3%E5%8E%8B%E7%BC%A9/"},{"name":"http/https代理服务器","slug":"http-https代理服务器","permalink":"https://tomsworkspace.github.io/tags/http-https%E4%BB%A3%E7%90%86%E6%9C%8D%E5%8A%A1%E5%99%A8/"},{"name":"修改注册表","slug":"修改注册表","permalink":"https://tomsworkspace.github.io/tags/%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8/"},{"name":"更改IE代理","slug":"更改IE代理","permalink":"https://tomsworkspace.github.io/tags/%E6%9B%B4%E6%94%B9IE%E4%BB%A3%E7%90%86/"},{"name":"IE代理设置","slug":"IE代理设置","permalink":"https://tomsworkspace.github.io/tags/IE%E4%BB%A3%E7%90%86%E8%AE%BE%E7%BD%AE/"},{"name":"-修改注册表立即生效","slug":"修改注册表立即生效","permalink":"https://tomsworkspace.github.io/tags/%E4%BF%AE%E6%94%B9%E6%B3%A8%E5%86%8C%E8%A1%A8%E7%AB%8B%E5%8D%B3%E7%94%9F%E6%95%88/"},{"name":"MIPs","slug":"MIPs","permalink":"https://tomsworkspace.github.io/tags/MIPs/"},{"name":"Mips寄存器","slug":"Mips寄存器","permalink":"https://tomsworkspace.github.io/tags/Mips%E5%AF%84%E5%AD%98%E5%99%A8/"},{"name":"GPU","slug":"GPU","permalink":"https://tomsworkspace.github.io/tags/GPU/"},{"name":"CPU与GPU","slug":"CPU与GPU","permalink":"https://tomsworkspace.github.io/tags/CPU%E4%B8%8EGPU/"},{"name":"图形图像处理器","slug":"图形图像处理器","permalink":"https://tomsworkspace.github.io/tags/%E5%9B%BE%E5%BD%A2%E5%9B%BE%E5%83%8F%E5%A4%84%E7%90%86%E5%99%A8/"},{"name":"智能硬件","slug":"智能硬件","permalink":"https://tomsworkspace.github.io/tags/%E6%99%BA%E8%83%BD%E7%A1%AC%E4%BB%B6/"},{"name":"linux-Arm","slug":"linux-Arm","permalink":"https://tomsworkspace.github.io/tags/linux-Arm/"},{"name":"page table","slug":"page-table","permalink":"https://tomsworkspace.github.io/tags/page-table/"},{"name":"页表","slug":"页表","permalink":"https://tomsworkspace.github.io/tags/%E9%A1%B5%E8%A1%A8/"},{"name":"PGD PTE","slug":"PGD-PTE","permalink":"https://tomsworkspace.github.io/tags/PGD-PTE/"},{"name":"四级页表","slug":"四级页表","permalink":"https://tomsworkspace.github.io/tags/%E5%9B%9B%E7%BA%A7%E9%A1%B5%E8%A1%A8/"},{"name":"Dll文件丢失","slug":"Dll文件丢失","permalink":"https://tomsworkspace.github.io/tags/Dll%E6%96%87%E4%BB%B6%E4%B8%A2%E5%A4%B1/"},{"name":"cache","slug":"cache","permalink":"https://tomsworkspace.github.io/tags/cache/"},{"name":"CPU cache","slug":"CPU-cache","permalink":"https://tomsworkspace.github.io/tags/CPU-cache/"},{"name":"三级缓存","slug":"三级缓存","permalink":"https://tomsworkspace.github.io/tags/%E4%B8%89%E7%BA%A7%E7%BC%93%E5%AD%98/"},{"name":"缓存映射","slug":"缓存映射","permalink":"https://tomsworkspace.github.io/tags/%E7%BC%93%E5%AD%98%E6%98%A0%E5%B0%84/"},{"name":"cache原理","slug":"cache原理","permalink":"https://tomsworkspace.github.io/tags/cache%E5%8E%9F%E7%90%86/"},{"name":"多级cache","slug":"多级cache","permalink":"https://tomsworkspace.github.io/tags/%E5%A4%9A%E7%BA%A7cache/"},{"name":"TLB","slug":"TLB","permalink":"https://tomsworkspace.github.io/tags/TLB/"},{"name":"TOM","slug":"TOM","permalink":"https://tomsworkspace.github.io/tags/TOM/"},{"name":"个人博客","slug":"个人博客","permalink":"https://tomsworkspace.github.io/tags/%E4%B8%AA%E4%BA%BA%E5%8D%9A%E5%AE%A2/"},{"name":"personal blog","slug":"personal-blog","permalink":"https://tomsworkspace.github.io/tags/personal-blog/"},{"name":"Hexo","slug":"Hexo","permalink":"https://tomsworkspace.github.io/tags/Hexo/"},{"name":"Github pages","slug":"Github-pages","permalink":"https://tomsworkspace.github.io/tags/Github-pages/"}]}