0%

  • toc
    {:toc}

安装

clang-format是一个格式化代码的工具, 支持多种代码风格。有两种方式使用:

安装:

sudo apt-get install clang-format
  • vim插件
Bundle 'rhysd/vim-clang-format'                                                                                                   

let g:clang_format#style_options = {
    \ "AccessModifierOffset" : -4,
    \ "AllowShortIfStatementsOnASingleLine" : "true",
    \ "AlwaysBreakTemplateDeclarations" : "true",
    \ "Standard" : "C++11"}

clang_format#style_options 可以在Clang-Format Style Options查找配置

  • 直接使用Clang-Format

.vimrc 中映射快捷键

map <C-K> :pyf <path-to-this-file>/clang-format.py<cr>
imap <C-K> <c-o>:pyf <path-to-this-file>/clang-format.py<cr>

需要在目录下建一个名为.clang-format的文件, 通过

clang-format -style=llvm -dump-config > .clang-format

可以获得一个模板 .clang-format 文件, 根据自己的需求修改即可

配置

获得模板

clang-format -style=llvm -dump-config > .clang-format

修改参考options

ref

  1. 使用clang-format格式化你的代码
  2. Clang-Format Style Options
  3. Objective-C代码自动格式化
  4. Clang-Format Style Options
  5. rhysd/vim-clang-format
  6. Vim Integration

编辑器

  • vim
  • emacs
  • kate(KDE下一个功能强大的编辑器)
  • Sublime text 3

集成开发环境

  • 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的根据你敲的代码快速模糊搜索并进行代码补全的引擎。

构建

  • make
  • CMake :跨平台的免费开源软件用于管理软件使用独立编译的方法进行构建的过程。
  • Bear :用于为clang工具生成编译数据库的工具
  • Biicode:基于文件的简单依赖管理器。
  • 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的源代码分析工具
  • Purify
  • Valgrind工具集(包括剖析工具Callgrind和线程分析工具Helgrind等)
  • KCachegrind
  • gprof开源剖析工具,通常作为gcc编译器的一部分
  • gprof2dot
  • Luke StackWalker ,查找程序性能瓶颈的工具
  • gperftools

代码工具

  • nm 列出来自对象文件的符号
  • objdump 显示对象文件信息
  • strings 列出二进制文件中可输出的字符串
  • strip 删除来自对象文件的符号
  • m4 宏处理程序
  • indent 代码格式化工具

检测工具

  • time 计时工具
  • ps 显示运行进程的当前状态
  • top 给出系统的详细信息
  • strace 记录对操作系统的所有访问,例如内存分配、文件I/O、系统调用和子进程的启动

冒泡排序

冒泡排序算法的运作如下:

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
void bubble_sort(int arr[], int len) {
    int i, j, temp;
    for (i = 0; i < len - 1; i++) {
        for (j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

改进算法

冒泡排序是与插入排序拥有相等的运行时间,但是两种算法在需要的交换次数却很大地不同。
在最好的情况,冒泡排序需要O(n^2)次交换,而插入排序只要最多O(n)交换。
冒泡排序的实现通常会对已经排序好的数列拙劣地运行(O(n^2)),而插入排序在这个例子只需要O(n)个运算。
因此很多现代的算法教科书避免使用冒泡排序,而用插入排序替换之。
冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最好的复杂度降低到O(n)。
在这个情况,已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。
有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。

改进代码,使用一个旗标来表示有无需要交换的可能

void bubble_sort(int arr[], int len) {
    int i, j, temp;
    bool flag = true;
    for (i = 0; i < len - 1; i++) {
        flag = false;
        for (j = 0; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                flag = true;
            }
        }

        if(!flag)
            break;
    }
}

进一步优化,如果R[0..i]已是有序区间,上次的扫描区间是R[i..n],
记上次扫描时最后一次执行交换的位置为lastSwapPos,则lastSwapPos在i与n之间,
不难发现R[i..lastSwapPos]区间也是有序的,否则这个区间也会发生交换;
所以下次扫描区间就可以由R[i..n] 缩减到[lastSwapPos..n]。

void bubble_sort(int arr[], int len) {
    int i, j, temp;
    int lastSwapPos = 0, lastSwapPosTemp = 0;
    for (i = 0; i < len - 1; i++) {
        lastSwapPos = lastSwapPosTemp;
        for (j = lastSwapPosTemp; j < len - 1 - i; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
                lastSwapPosTemp = j;
            }
        }

        if(lastSwapPos == lastSwapPosTemp)
            break;
    }
}

鸡尾酒排序

鸡尾酒排序,也就是定向冒泡排序,是冒泡排序的一种变形。
此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序
鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。

插入排序

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置中
  6. 重复步骤2~5

快速排序

  1. 从数列中挑出一个元素,称为 “基准”(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
    在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

#归并排序

归并排序是建立在归并操作上的一种有效的排序算法。
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用

步骤:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾

堆排序

堆积排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

选择排序

  1. 首先在未排序序列中找到最小元素,存放到排序序列的起始位置,
  2. 然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。
  3. 以此类推,直到所有元素均排序完毕。

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  1. 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
  2. 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位>

梳排序

梳排序是改良自冒泡排序和快速排序。

在冒泡排序算法中,只比较阵列中相邻的二项,即比较的二项的间距(Gap)是1,
梳排序提出此间距其实可大于1,改自插入排序的希尔排序同样提出相同观点。
梳排序中,开始时的间距设定为阵列长度,并在循环中以固定比率递减,通常递减率设定为1.24。
在一次循环中,梳排序如同冒泡排序一样把阵列从首到尾扫描一次,比较及交换两项,不同的是两项的间距不固定于1。
如果间距递减至1,梳排序假定输入阵列大致排序好,并以冒泡排序作最后检查及修正。

Ref

  1. GIFs of sorting algorithms
  2. Sorting Algorithm Animations
  3. This gif, visualizing a sorting algorithm
  4. 15 Sorting Algorithms in 6 Minutes
  5. 视觉直观感受 7 种常用的排序算法
  6. 排序算法
  7. 插入排序
  8. sorting algorithms
  9. comparisonSort
  10. visualgo
  11. sort algo
  12. sortvis
  13. sort vis tool


FreeType 中函数 FT_Get_Kerning

  • Backtrack(后迹) — This is the sequence of things that occur before the target in the rule.
    This sequence can be composed of glyphs, classes or a mix of both.
  • Lookahead(前瞻) — This is the sequence of glyphs that occur after the target in the rule.
    Like the backtrack, this sequence can be composed of glyphs, classes or a mix of both.

名词

FontCreator字库编辑工具,使用Ctrl-F8(OpenType设计工具)可以编辑TTF字库相关信息:

GSUB

The OpenType Layout Feature specification
describes eight types of substitution lookups in the glyph substitution table (GSUB).
The following table shows those currently supported by FontCreator:

  1. Single Substitute a single glyph by another single glyph (a -> b)
  2. Multiple Substitute a single glyph by other multiple glyphs (a -> xyz)
  3. Alternate Substitute a single glyph by one of multiple alternates (a -> x or y or z)
  4. Ligature Substitute multiple glyphs by a single ligature (f f i -> ffi)
  5. Context Substitute one or more glyphs in context
  6. Chaining Context Substitute context specific glyphs (3rd -> 3rd)
  7. Extension Substitution
  8. Reverse chaining context Applied in reverse order, replace single glyph in chaining context

GPOS

The OpenType Layout Feature specification
describes nine types of positioning lookups in the glyph positioning table (GPOS).
The following table shows those currently supported by FontCreator:

  1. Single adjustment Change the position of a single glyph (sub/superscript)
  2. Pair adjustment Mostly used to define kerning pairs
  3. Cursive attachment
  4. Mark to base attachment Attach a combining mark such as a diacritic to a base glyph
  5. Mark to ligature attachment Attach a combining mark to a ligature
  6. Mark to mark attachment Attach a combining mark to another mark
  7. Context Positioning Position one or more glyphs in context
  8. Chained Context Positioning Position one or more glyphs in chained context
  9. Extension Substitution

entries

字体 -> OpenType设计工具

Refs

  1. FontCreator Help
  2. OpenType Layout Features - Supported types of substitution and positioning
  3. Developing OpenType Fonts for Khmer Script (3 of 3): Features
  4. OpenType Designer
  5. GSUB - The Glyph Substitution Table
  6. opentypecookbook
  7. Recommendations for OpenType Fonts


Fiddler工具可以查看HTTP Request和Response,
还可以方便地查看Response中的状态码,
如果不熟悉这个工具,可以先参考Fiddler教程

为了重现HTTP 状态码,本文会使用Fiddler Composer来创建“特殊的HTTP Request”.
可以参考Fiddler Composer创建和发送HTTP Request

常见的状态码:

  • 200 OK 服务器成功处理了请求(这个是我们见到最多的)
  • 301/302 Moved Permanently(重定向)请求的URL已移走。Response中应该包含一个Location URL, 说明资源现在所处的位置
  • 304 Not Modified(未修改)客户的缓存资源是最新的, 要客户端使用缓存
  • 404 Not Found 未找到资源
  • 501 Internal Server Error服务器遇到一个错误,使其无法对请求提供服务

1XX 信息性状态码

这些状态码是HTTP 1.1引入的。对于这些状态码的价值还存在争论。

  • 100 收到了请求的起始部分,客户端应该继续请求
  • 101 服务器正根据客户端的指示将协议切换成Update Header列出的协议

2XX 成功状态码

  • 200 服务器成功处理了请求
  • 201 对于那些要服务器创建对象的请求来说,资源已创建完毕
  • 202 请求已接受, 但服务器尚未处理
  • 203 服务器已将事务成功处理,只是实体Header包含的信息不是来自原始服务器,而是来自资源的副本
  • 204 Response中包含一些Header和一个状态行, 但不包括实体的主题内容(没有response body)
  • 205 另一个主要用于浏览器的代码。意思是浏览器应该重置当前页面上所有的HTML表单
  • 206 部分请求成功

3XX 重定向状态码

重定向状态码用来告诉浏览器客户端,它们访问的资源已被移动,
Web服务器发送一个重定向状态码和一个可选的Location Header, 告诉客户端新的资源地址在哪。

浏览器客户端会自动用Location中提供的地址,重新发送新的Request。 这个过程对用户来说是透明的。

301和302 非常相似, 一个是永久转移,一个是临时转移。 (在我们看来, 这两个没太大区别)

302,303,307 是一样。
这是因为302是HTTP 1.0定义的, HTTP1.1中使用303,307.
同时又保留了302. (但在现实中,我们还是用302,我是没见过303和307)
所以这一节, 我们只需要掌握302, 304 就可以了

  • 300 客户端请求了实际指向多个资源的URL。这个代码是和一个选项列表一起返回的,然后用户就可以选择他希望的选项了
  • 301 请求的URL已移走。Response中应该包含一个Location URL, 说明资源现在所处的位置状态码301
  • 302 与状态码301类似。但这里的移除是临时的。 客户端会使用Location中给出的URL,重新发送新的HTTP requestHTTP协议详解-302
  • 303 类似302
  • 304 客户的缓存资源是最新的, 要客户端使用缓存HTTP协议之缓存-304
  • 305 必须通过代理访问资源, 代理的地址在Response 的Location中
  • 306 这个状态码当前没使用
  • 307 临时重定向类似302

4XX 客户端错误状态码

  • 400 Bad Request(坏请求)告诉客户端,它发送了一个错误的请求。状态码400
  • 401 Unauthorized(未授权)需要客户端对自己认证HTTP协议之基本认证-401
  • 402 Payment Required(要求付款)这个状态还没被使用, 保留给将来用
  • 403 Forbidden(禁止)请求被服务器拒绝了状态码403
  • 404 Not Found(未找到)未找到资源HTTP协议详解-404
  • 405 Method Not Allowed(不允许使用的方法)不支持该Request的方法。状态码405
  • 406 Not Acceptable(无法接受)
  • 407 Proxy Authentication Required(要求进行代理认证)与状态码401类似, 用于需要进行认证的代理服务器HTTP协议之代理-407
  • 408 Request Timeout(请求超时) 如果客户端完成请求时花费的时间太长, 服务器可以回送这个状态码并关闭连接
  • 409 Conflict(冲突)发出的请求在资源上造成了一些冲突
  • 410 Gone(消失了)服务器曾经有这个资源,现在没有了, 与状态码404类似
  • 411 Length Required(要求长度指示)服务器要求在Request中包含Content-Length。状态码411
  • 412 Precondition Failed(先决条件失败)
  • 413 Request Entity Too Large(请求实体太大)客户端发送的实体主体部分比服务器能够或者希望处理的要大状态码413
  • 414 Request URI Too Long(请求URI太长)客户端发送的请求所携带的URL超过了服务器能够或者希望处理的长度状态码414
  • 415 Unsupported Media Type(不支持的媒体类型)服务器无法理解或不支持客户端所发送的实体的内容类型
  • 416 Requested Range Not Satisfiable(所请求的范围未得到满足)
  • 417 Expectation Failed(无法满足期望)

5XX 服务器错误状态码

  • 500 Internal Server Error(内部服务器错误)服务器遇到一个错误,使其无法为请求提供服务状态码500
  • 501 Not Implemented(未实现)客户端发起的请求超出服务器的能力范围(比如,使用了服务器不支持的请求方法)时,使用此状态码。状态码501
  • 502 Bad Gateway(网关故障)代理使用的服务器遇到了上游的无效响应状态码502
  • 503 Service Unavailable(未提供此服务)服务器目前无法为请求提供服务,但过一段时间就可以恢复服务
  • 504 Gateway Timeout(网关超时)与状态吗408类似, 但是响应来自网关或代理,此网关或代理在等待另一台服务器的响应时出现了超时
  • 505 HTTP Version Not Supported(不支持的HTTP版本)服务器收到的请求使用了它不支持的HTTP协议版本。 有些服务器不支持HTTP早期的HTTP协议版本,也不支持太高的协议版本
  1. HTTP协议之状态码详解
  2. 2XX



  1. 网络基本功系列


软件工程学院的研究表明,程序员们会犯15-20种常见的错误。
所以,通过把这些错误加入到检查清单当中,你可以确保不论什么时候,
只要这些错误发生了,你就能发现它们,并且可以帮助你杜绝这些错误。

为了帮助你开始创建一个清单,这里列出了一些典型的内容:代码审查清单。

常规项

  • 代码能够工作么?它有没有实现预期的功能,逻辑是否正确等。
  • 所有的代码是否简单易懂?
  • 代码符合你所遵循的编程规范么?这通常包括大括号的位置,变量名和函数名,行的长度,缩进,格式和注释。
  • 是否存在多余的或是重复的代码?
  • 代码是否尽可能的模块化了?
  • 是否有可以被替换的全局变量?
  • 是否有被注释掉的代码?
  • 循环是否设置了长度和正确的终止条件?
  • 是否有可以被库函数替代的代码?
  • 是否有可以删除的日志或调试代码?

安全

  • 所有的数据输入是否都进行了检查(检测正确的类型,长度,格式和范围)并且进行了编码?
  • 在哪里使用了第三方工具,返回的错误是否被捕获?
  • 输出的值是否进行了检查并且编码?
  • 无效的参数值是否能够处理?

文档

  • 是否有注释,并且描述了代码的意图?
  • 所有的函数都有注释吗?
  • 对非常规行为和边界情况处理是否有描述?
  • 第三方库的使用和函数是否有文档?
  • 数据结构和计量单位是否进行了解释?
  • 是否有未完成的代码?如果是的话,是不是应该移除,或者用合适的标记进行标记比如‘TODO’?

测试

  • 代码是否可以测试?比如,不要添加太多的或是隐藏的依赖关系,不能够初始化对象,测试框架可以使用方法等。
  • 是否存在测试,它们是否可以被理解?比如,至少达到你满意的代码覆盖(code coverage)。
  • 单元测试是否真正的测试了代码是否可以完成预期的功能?
  • 是否检查了数组的“越界“错误?
  • 是否有可以被已经存在的API所替代的测试代码?

总结

你同样需要把特定语言中有可能引起错误的问题添加到清单中。

这个清单故意没有详尽的列出所有可能会发生的错误。
你不希望你的清单是这样的,太长了以至于从来没人会去用它。仅仅包含常见的问题会比较好。

优化你的清单

把使用清单作为你的起点,针对特定的使用案例,你需要对其进行优化。
一个比较棒的方式就是让你的团队记录下那些在代码审查过程中临时发现的问题,
有了这些数据,你就能够确定你的团队常犯的错误,然后你就可以量身定制一个审查清单。
确保你删除了那些没有出现过的错误。(你也可以保留那些出现概率很小,但是非常关键的项目,比如安全相关的问题)。

得到认可并且保持更新

基本规则是,清单上的任何条目都必须明确,而且,如果可能的话,对于一些条目你可以对其进行二元判定。
这样可以防止判断的不一致。和你的团队分享这份清单并且让他们认同你清单的内容是个好主意。
同样的,要定期检查你的清单,以确保各条目仍然是有意义的。

有了一个好的清单,可以提高你在代码审查过程中发现的缺陷个数。
这可以帮助你提高代码标准,避免质量参差不齐的代码审查。

  1. 程序员必备的代码审查清单

git rebase

git rebase是对commit history的改写
当你要改写的commit history还没有被提交到远程repo的时候,
也就是说,还没有与他人共享之前,commit history是你私人所有的,那么想怎么改写都可以

一旦被提交到远程后,这时如果再改写history,那么势必和他人的history长的就不一样了
git push的时候,git会比较commit history,如果不一致,
commit动作会被拒绝,唯一的办法就是带上-f参数,
强制要求commit,这时git会以committer的history覆写远程repo,
从而完成代码的提交。虽然代码提交上去了,
但是这样可能会造成别人工作成果的丢失,所以使用-f参数要慎重

注意点

  1. 除非只有自己一个人用,不然用 push –force 的都该去死
  2. 慎用 push -f!
  3. 每个人提交前,都应该把自己的修改rebase到服务器的最新代码之上,遵守这个规则就不会有任何问题。
    如果你需要force push,说明你做反了,把服务器代码rebase到你本地分支之上才会需要force push,这是错误的用法。
  4. 一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作
  5. 如果 rebase 完后,需要使用 push -f 的话,一定代表该 rebase 操作是不合适。除非你是有意在修改提交历史
  6. 除非某个分支只有你自己搞,你怎么rebase都是没有问题的,但是如果你在master或者develop这种分支上来rebase,
    估计团队里每个人都想拍死你
  7. 当你在某个分支进行团队合作的时候, 常用rebase真的是不合理。而且容易出问题。慎用 push –force
  8. git rebase 一般自己一个人开发时使用,用来保持提交记录的整洁。一旦上传到github后,不应该使用git rebase
  9. 一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。
    如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你
  10. 不需要push -f啊,如果分支落后就用pull –rebase

例子

假设楼主的team中有两个developer:tom和jerry,他们共同使用一个远程repo,
并各自clone到自己的机器上,为了简化描述,这里假设只有一个branch:master。

这时tom机器的repo有两个branch: master, origin/master

而jerry的机器上也是有两个branch: master, origin/master

均如下图所示

1

tom和jerry分别各自开发自己的新feature,
不断有新的commit提交到他们各自私有的commit history中,
所以他们的master指针不断的向前推移,分别指向不同的commit。
而又由于他们都没有git fetch和git push,所以他们的origin/master都维持不变

jerry的repo如下

2

tom的repo如下,注意T1和上图的J1,分别是两个不同的commit

3

这时Tom首先把他的commit提交的远程repo中,那么他本机origin/master指针则会前进,和master指针保持一致,如下

4

远程repo如下

5

现在jerry也想把他的commit提交到远程repo上去,
运行git push,毫无意外的失败了,所以他git fetch了一下,把远程repo,也就是之前tom提交的T1给拉到了他本机repo中,如下

6

commit history出现了分叉,要想把tom之前提交的内容包含到自己的工作中来,
有一个方法就是git merge,它会自动生成一个commit,既包含tom的提交,也包含jerry的提交
这样就把两个分叉的commit重新又合并在一起。但是这个自动生成的commit会有两个parent
review代码的时候必须要比较两次,很不方便。

jerry为了保证commit history的线性,决定采用另外一种方法,就是git rebase
jerry的提交J1这时还没有被提交到远程repo上去,也就是他完全私有的一个commit,
所以使用git rebase改写J1的history完全没有问题
,改写之后,如下

7

注意J1被改写到T1后面了,变成了J1`

git push后,本机repo

8

而远程repo

9

异常的轻松,一条直线,没有-f

所以,在不用-f的前提下,想维持树的整洁,方法就是:在git push之前,先git fetch,再git rebase

git fetch origin master
git rebase origin/master
git push
  1. 10 个迅速提升你 Git 水平的提示
  2. 团队开发里频繁使用 git rebase 来保持树的整洁好吗?
  3. a-successful-git-branching-model
  4. Git-分支-分支的衍合
  5. 构造干净的 Git 历史线索
  6. Github workflow

冒泡排序

  1. 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
  2. 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。这步做完后,最后的元素会是最大的数。
  3. 针对所有的元素重复以上的步骤,除了最后一个。
  4. 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。

bubble1

bubble2

冒泡排序是与插入排序拥有相等的运行时间,但是两种算法在需要的交换次数却很大地不同。
在最好的情况,冒泡排序需要O(n^2)次交换,而插入排序只要最多O(n)交换。
冒泡排序的实现通常会对已经排序好的数列拙劣地运行(O(n^2)),而插入排序在这个例子只需要O(n)个运算。
因此很多现代的算法教科书避免使用冒泡排序,而用插入排序替换之。
冒泡排序如果能在内部循环第一次运行时,使用一个旗标来表示有无需要交换的可能,也可以把最好的复杂度降低到O(n)。
在这个情况,已经排序好的数列就无交换的需要。若在每次走访数列时,把走访顺序反过来,也可以稍微地改进效率。
有时候称为鸡尾酒排序,因为算法会从数列的一端到另一端之间穿梭往返。

鸡尾酒排序

鸡尾酒排序,也就是定向冒泡排序,是冒泡排序的一种变形。
此算法与冒泡排序的不同处在于排序时是以双向在序列中进行排序
鸡尾酒排序等于是冒泡排序的轻微变形。不同的地方在于从低到高然后从高到低,而冒泡排序则仅从低到高去比较序列里的每个元素。

shaker

插入排序

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果该元素(已排序)大于新元素,将该元素移到下一位置
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置中
  6. 重复步骤2~5

Insertion

Insertion

快速排序

  1. 从数列中挑出一个元素,称为 “基准”(pivot),
  2. 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。
    在这个分区退出之后,该基准就处于数列的中间位置。这个称为分区(partition)操作。
  3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。

排序效果:

quick

归并排序

归并排序是建立在归并操作上的一种有效的排序算法。
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用

步骤:

  1. 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  2. 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  3. 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  4. 重复步骤3直到某一指针达到序列尾
  5. 将另一序列剩下的所有元素直接复制到合并序列尾

merge

merge

堆排序

堆积排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。
堆是一个近似完全二叉树的结构,并同时满足堆性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

heap

选择排序

  1. 首先在未排序序列中找到最小元素,存放到排序序列的起始位置,
  2. 然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。
  3. 以此类推,直到所有元素均排序完毕。

selection

希尔排序

希尔排序,也称递减增量排序算法,是插入排序的一种高速而稳定的改进版本。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:

  1. 插入排序在对几乎已经排好序的数据操作时, 效率高, 即可以达到线性排序的效率
  2. 但插入排序一般来说是低效的, 因为插入排序每次只能将数据移动一位>

shell

梳排序

梳排序是改良自冒泡排序和快速排序。

在冒泡排序算法中,只比较阵列中相邻的二项,即比较的二项的间距(Gap)是1,
梳排序提出此间距其实可大于1,改自插入排序的希尔排序同样提出相同观点。
梳排序中,开始时的间距设定为阵列长度,并在循环中以固定比率递减,通常递减率设定为1.24。
在一次循环中,梳排序如同冒泡排序一样把阵列从首到尾扫描一次,比较及交换两项,不同的是两项的间距不固定于1。
如果间距递减至1,梳排序假定输入阵列大致排序好,并以冒泡排序作最后检查及修正。

comb

  1. GIFs of sorting algorithms
  2. Sorting Algorithm Animations
  3. This gif, visualizing a sorting algorithm
  4. 15 Sorting Algorithms in 6 Minutes
  5. 视觉直观感受 7 种常用的排序算法
  6. 排序算法
  7. 插入排序
  8. sorting algorithms
  9. comparisonSort
  10. visualgo
  11. sort algo
  12. sortvis
  13. sort vis tool

作者:卢钧轶(cenalulu) 本文原文地址:http://cenalulu.github.io/linux/all-vim-cheatsheat/

经典版

下面这个键位图应该是大家最常看见的经典版了。
其实这个版本是一系列的入门教程键位图的组合结果。
要查看不同编辑模式下的键位图,可以看这里打包下载
此外,这里还有简体中文版。


入门版

基本操作的入门版。
原版出处还有keynote版本可供DIY以及其他相关有用的cheatsheet。


进阶版

下图是300DPI的超清大图,
另外查看原文还有更多版本:黑白,低分辨率,色盲等


增强版

下图是一个更新时间较新的现代版,含有的信息也更丰富。
原文链接


文字版

原文链接

作者:卢钧轶(cenalulu) 本文原文地址:http://cenalulu.github.io/linux/about-denormalized-float-number/

本文从一个有趣而又令人意外的实验展开,介绍一些关于浮点数你应该知道的基础知识

一个有趣的实验

本文从一个有趣而诡异的实验开始。最早这个例子博主是从 Stackoverflow上的一个问题中看到的。
为了提高可读性,博主这里做了改写,简化成了以下两段代码:

#include <iostream>
#include <string>
using namespace std;

int main() {
    const float x=1.1;
    const float z=1.123;
    float y=x;
    for(int j=0;j<90000000;j++)
    {
        y*=x;
        y/=z;
        y+=0.1f;
        y-=0.1f;
    }
    return 0;
}


#include <iostream>
#include <string>
using namespace std;

int main() {
    const float x=1.1;
    const float z=1.123;
    float y=x;
    for(int j=0;j<90000000;j++)
    {
        y*=x;
        y/=z;
        y+=0;
        y-=0;
    }
    return 0;
}

上面两段代码的唯一差别就是第一段代码中y+=0.1f,而第二段代码中是y+=0。
由于y会先加后减同样一个数值,照理说这两段代码的作用和效率应该是完全一样的,当然也是没有任何逻辑意义的。
假设现在我告诉你:其中一段代码的效率要比另一段慢7倍。想必读者会认为一定是y+=0.1f的那段慢,毕竟它和y+=0相比看上去要多一些运算。
但是,实验结果,却出乎意料, y+=0的那段代码比y+=0.1f足足慢了7倍。
世界观被颠覆了有木有?博主是在自己的Macbook Pro上进行的测试,有兴趣的读者也可以在自己的笔记本上试试。
(只要是支持SSE2指令集的CPU都会有相似的结果)。

shell> g++ code1.c -o test1
shell> g++ code2.c -o test2
shell> time ./test1

real    0m1.490s
user    0m1.483s
sys     0m0.003s

shell> time ./test2

real    0m9.895s
user    0m9.871s
sys     0m0.009s

当然 原文中的投票最高的回答解释的非常好,但博主第一次看的时候是一头雾水,因为大部分基础知识已经还给大学老师了。
所以,本着知其然还要知其所以然的态度,博主做了一个详尽的分析和思路整理过程。也希望读者能够从0开始解释这个诡异现象的原因。

复习浮点数的二进制转换

现在让我们复习大学计算机基础课程。
如果你熟练掌握了浮点数向二进制表达式转换的方法,那么你可以跳过这节。
我们先来看下浮点数二进制表达的三个组成部分。

mmap

三个主要成分是:

  • Sign(1bit):表示浮点数是正数还是负数。0表示正数,1表示负数
  • Exponent(8bits):指数部分。类似于科学技术法中的M*10^N中的N,只不过这里是以2为底数而不是10。
    需要注意的是,这部分中是以2^7-1即127,也即01111111代表2^0,转换时需要根据127作偏移调整。
  • Mantissa(23bits):基数部分。浮点数具体数值的实际表示。

下面我们来看个实际例子来解释下转换过程。

  • Step 1 改写整数部分 以数值5.2为例。先不考虑指数部分,我们先单纯的将十进制数改写成二进制。 整数部分很简单,5.101.
  • Step 2 改写小数部分 小数部分我们相当于拆成是2^-1一直到2^-N的和。
    例如: 0.2 = 0.125+0.0625+0.007825+0.003906252^-3+2^-4+2^-7+2^-8....,也即.00110011001100110011
  • Step 3 规格化 现在我们已经有了这么一串二进制101.00110011001100110011
    然后我们要将它规格化,也叫Normalize。其实原理很简单就是保证小数点前只有一个bit。
    于是我们就得到了以下表示:1.0100110011001100110011 * 2^2
    到此为止我们已经把改写工作完成,接下来就是要把bit填充到三个组成部分中去了。
  • Step 4 填充 指数部分(Exponent):之前说过需要以127作为偏移量调整。
    因此2的2次方,指数部分偏移成2+127即129,表示成10000001填入。
    整数部分(Mantissa):除了简单的填入外,需要特别解释的地方是1.010011中的整数部分1在填充时被舍去了。
    因为规格化后的数值整部部分总是为1。
    那大家可能有疑问了,省略整数部分后岂不是1.0100110.010011就混淆了么?
    其实并不会,如果你仔细看下后者:会发现他并不是一个规格化的二进制,可以改写成1.0011 * 2^-2
    所以省略小数点前的一个bit不会造成任何两个浮点数的混淆。 具体填充后的结果见下图

mmap

练习:如果想考验自己是否充分理解这节内容的话,可以随便写一个浮点数尝试转换。通过 浮点二进制转换工具可以验证答案。

什么是Denormalized Number

了解完浮点数的表达以后,不难看出浮点数的精度和指数范围有很大关系。
最低不能低过2^-7-1最高不能高过2^8-1(其中剔除了指数部分全0和全1的特殊情况)。
如果超出表达范围那么不得不舍弃末尾的那些小数,我们成为overflow和underflow。
甚至有时舍弃都无法表示,例如当我们要表示一个:1.00001111*2^-7这样的超小数值的时候就无法用规格化数值表示,
如果不想点其他办法的话,CPU内部就只能把它当做0来处理。
那么,这样做有什么问题呢?最显然易见的一种副作用就是:当多次做低精度浮点数舍弃的后,就会出现除数为0的exception,导致异常。
当然精度失准严重起来也可以要人命,以下这个事件摘自wikipedia

On 25 February 1991, a loss of significance in a MIM-104 Patriot missile battery prevented 
it intercepting an incoming Scud missile in Dhahran, Saudi Arabia, 
contributing to the death of 28 soldiers from the U.S. 
Army’s 14th Quartermaster Detachment.[25] See also: Failure at Dhahran

于是乎就出现了Denormalized Number(后称非规格化浮点)
他和规格浮点的区别在于,规格浮点约定小数点前一位默认是1。而非规格浮点约定小数点前一位可以为0
这样小数精度就相当于多了最多2^22范围。

但是,精度的提升是有代价的。由于CPU硬件只支持,或者默认对一个32bit的二进制使用规格化解码。
因此需要支持32bit非规格数值的转码和计算的话,需要额外的编码标识,也就是需要额外的硬件或者软件层面的支持。
以下是wiki上的两端摘抄,说明了非规格化计算的效率非常低。
一般来说,由软件对非规格化浮点数进行处理将带来极大的性能损失,而由硬件处理的情况会稍好一些,
但在多数现代处理器上这样的操作仍是缓慢的。极端情况下,规格化浮点数操作可能比硬件支持的非规格化浮点数操作快100倍。

For example when using NVIDIA’s CUDA platform, on gaming cards, 
calculations with double precision take 3 to 24 times longer to complete than calculations using single precision.

如果要解释为什么有如此大的性能损耗,那就要需要涉及电路设计了,超出了博主的知识范围。
当然万能的wiki也是有答案的,有兴趣的读者可以自行查阅。

回到实验

总上面的分析中我们得出了以下结论:

  • 浮点数表示范围有限,精度受限于指数和底数部分的长度,超过精度的小数部分将会被舍弃(underflow)
  • 为了表示更高精度的浮点数,出现了非规格化浮点数,但是他的计算成本非常高。
  • 于是我们就可以发现通过几十上百次的循环后,y中存放的数值无限接近于零。
    CPU将他表示为精度更高的非规格化浮点。
    而当y+0.1f时为了保留跟重要的底数部分,之后无限接近0(也即y之前存的数值)被舍弃,当y-0.1f后,y又退化为了规格化浮点数。
    并且之后的每次y*xy/z时,CPU都执行的是规划化浮点运算。
    而当y+0,由于加上0值后的y仍然可以被表示为非规格化浮点,因此整个循环的四次运算中CPU都会使用非规格浮点计算,效率就大大降低了。

其他

当然,也有在程序内部也是有办法控制非规范化浮点的使用的。
在相关程序的上下文中加上fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
就可以迫使CPU放弃使用非规范化浮点计算,提高性能。
我们用这种办法修改上面实验中的代码后,y+=0的效率就和y+=0.1f就一样了。
甚至还比y+=0.1f更快了些,世界观又端正了不是么:) 修改后的代码如下

#include <iostream>
#include <string>
#include <fenv.h>
using namespace std;

int main() {
    fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
    const float x=1.1;
    const float z=1.123;
    float y=x;
    for(int j=0;j<90000000;j++)
    {
        y*=x;
        y/=z;
        y+=0;
        y-=0;
    }
    return 0;
}

Reference

什么是非规格化浮点数
Why does changing 0.1f to 0 slow down performance by 10x? IEEE floating point Floating point Denormal number

程序优化是指程序效率的优化。一般来说,程序优化主要是以下三个步骤:

  1. 算法优化
  2. 代码优化
  3. 指令优化

算法优化

算法上的优化是必须首要考虑的,也是最重要的一步。
一般我们需要分析算法的时间复杂度,即处理时间与输入数据规模的一个量级关系,
一个优秀的算法可以将算法复杂度降低若干量级,
那么同样的实现,其平均耗时一般会比其他复杂度高的算法少(这里不代表任意输入都更快)。

比如说排序算法,快速排序的时间复杂度为O(nlogn),
而插入排序的时间复杂度为O(n*n),那么在统计意义下,快速排序会比插入排序快,
而且随着输入序列长度n的增加,两者耗时相差会越来越大。
但是,假如输入数据本身就已经是升序(或降序),那么实际运行下来,快速排序会更慢。

代码优化

代码优化一般需要与算法优化同步进行,代码优化主要是涉及到具体的编码技巧。
同样的算法与功能,不同的写法也可能让程序效率差异巨大。
一般而言,代码优化主要是针对循环结构进行分析处理。

避免循环内部的乘(除)法以及冗余计算

能把运算放在循环外的尽量提出去放在外部,循环内部不必要的乘除法可使用加法来替代等。

避免循环内部有过多依赖和跳转,使cpu能流水起来

关于CPU流水线技术可google/baidu,循环结构内部计算或逻辑过于复杂,将导致cpu不能流水,
那这个循环就相当于拆成了n段重复代码的效率。

for(int i = 0; i < N; i++)
{
    if(i < 100) a[i] += 5;
    else if(i < 200) a[i] += 10;
    else a[i] += 20;
}

优化为:

for(int i = 0; i < 100; i++)
{
    a[i] += 5;
}
for(int i = 100; i < 200; i++)
{
    a[i] += 10;
}
for(int i = 200; i < N; i++)
{
    a[i] += 20;
}

定点化

定点化的思想是将浮点运算转换为整型运算,
定点化的做法是将数据乘上一个很大的数后,将所有运算转换为整数计算。
例如某个乘法我只关心小数点后3位,那把数据都乘上10000后,进行整型运算的结果也就满足所需的精度了。

以空间换时间

空间换时间最经典的就是查表法了,某些计算相当耗时,但其自变量的值域是比较有限的,
这样的情况可以预先计算好每个自变量对应的函数值,存在一个表格中,每次根据自变量的值去索引对应的函数值即可。

预分配内存

预分配内存主要是针对需要循环处理数据的情况的。
比如视频处理,每帧图像的处理都需要一定的缓存,如果每帧申请释放,则势必会降低算法效率

指令优化

略。

如何定位程序热点

程序热点是指程序中最耗时的部分,一般程序优化工作都是优先去优化热点部分,那么如何来定位程序热点呢?

一般而言,主要有2种方法,一种是通过观察与分析,通过分析算法,自然能知道程序热点;
另一方面,观察代码结构,一般具有最大循环的地方就是热点,这也是前面那些优化手段都针对循环结构的原因。
另一种方法就是利用工具来找程序热点。

问题描述

编程语言书籍中经常解释值类型被创建在栈上,引用类型被创建在堆上,但是并没有本质上解释这堆和栈是什么。
我仅有高级语言编程经验,没有看过对此更清晰的解释。
我的意思是我理解什么是栈,但是它们到底是什么,在哪儿呢(站在实际的计算机物理内存的角度上看)?
在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗?
它们的作用范围是什么?
它们的大小由什么决定?
哪个更快?

答案一

栈是为执行线程留出的内存空间。当函数被调用的时候,栈顶为局部变量和一些 bookkeeping 数据预留块。
当函数执行完毕,块就没有用了,可能在下次的函数调用的时候再被使用。
栈通常用后进先出(LIFO)的方式预留空间;
因此最近的保留块(reserved block)通常最先被释放。
这么做可以使跟踪堆栈变的简单;从栈中释放块(free block)只不过是指针的偏移而已。

堆(heap)是为动态分配预留的内存空间。
和栈不一样,从堆上分配和重新分配块没有固定模式;
你可以在任何时候分配和释放它。这样使得跟踪哪部分堆已经被分配和被释放变的异常复杂;
有许多定制的堆分配策略用来为不同的使用模式下调整堆的性能。

每一个线程都有一个栈,但是每一个应用程序通常都只有一个堆(尽管为不同类型分配内存使用多个堆的情况也是有的)。

直接回答你的问题:

  1. 当线程创建的时候,操作系统(OS)为每一个系统级(system-level)的线程分配栈。
    通常情况下,操作系统通过调用语言的运行时(runtime)去为应用程序分配堆。
  2. 栈附属于线程,因此当线程结束时栈被回收。
    堆通常通过运行时在应用程序启动时被分配,当应用程序(进程)退出时被回收。
  3. 当线程被创建的时候,设置栈的大小。
    在应用程序启动的时候,设置堆的大小,但是可以在需要的时候扩展(分配器向操作系统申请更多的内存)。
  4. 栈比堆要快,因为它存取模式使它可以轻松的分配和重新分配内存(指针/整型只是进行简单的递增或者递减运算),
    然而堆在分配和释放的时候有更多的复杂的 bookkeeping 参与。
    另外,在栈上的每个字节频繁的被复用也就意味着它可能映射到处理器缓存中,所以很快(译者注:局部性原理)。

答案二

Stack

  • 和堆一样存储在计算机 RAM 中。
  • 在栈上创建变量的时候会扩展,并且会自动回收。
  • 相比堆而言在栈上分配要快的多。
  • 用数据结构中的栈实现。
  • 存储局部数据,返回地址,用做参数传递。
  • 当用栈过多时可导致栈溢出(无穷次(大量的)的递归调用,或者大量的内存分配)。
  • 在栈上的数据可以直接访问(不是非要使用指针访问)。
  • 如果你在编译之前精确的知道你需要分配数据的大小并且不是太大的时候,可以使用栈。
  • 当你程序启动时决定栈的容量上限。

Heap

  • 和栈一样存储在计算机RAM。
  • 在堆上的变量必须要手动释放,不存在作用域的问题。数据可用 delete, delete[] 或者 free 来释放。
  • 相比在栈上分配内存要慢。
  • 通过程序按需分配。
  • 大量的分配和释放可造成内存碎片。
  • 在 C++ 中,在堆上创建数的据使用指针访问,用 new 或者 malloc 分配内存。
  • 如果申请的缓冲区过大的话,可能申请失败。
  • 在运行期间你不知道会需要多大的数据或者你需要分配大量的内存的时候,建议你使用堆。
  • 可能造成内存泄露。

答案三

堆和栈是两种内存分配的两个统称。可能有很多种不同的实现方式,但是实现要符合几个基本的概念:

  1. 对栈而言,栈中的新加数据项放在其他数据的顶部,移除时你也只能移除最顶部的数据(不能越位获取)。
  2. 对堆而言,数据项位置没有固定的顺序。你可以以任何顺序插入和删除,因为他们没有“顶部”数据这一概念。

在通常情况下由操作系统(OS)和语言的运行时(runtime)控制吗?

如前所述,堆和栈是一个统称,可以有很多的实现方式。
计算机程序通常有一个栈叫做调用栈,
用来存储当前函数调用相关的信息(比如:主调函数的地址,局部变量),
因为函数调用之后需要返回给主调函数。
栈通过扩展和收缩来承载信息。
实际上,程序不是由运行时来控制的,它由编程语言、操作系统甚至是系统架构来决定。

堆是在任何内存中动态和随机分配的(内存的)统称;
也就是无序的。内存通常由操作系统分配,通过应用程序调用 API 接口去实现分配。
在管理动态分配内存上会有一些额外的开销,不过这由操作系统来处理。

它们的作用范围是什么?

调用栈是一个低层次的概念,就程序而言,它和“作用范围”没什么关系。
如果你反汇编一些代码,你就会看到指针引用堆栈部分。
就高级语言而言,语言有它自己的范围规则。
一旦函数返回,函数中的局部变量会直接直接释放。你的编程语言就是依据这个工作的。

在堆中,也很难去定义。
作用范围是由操作系统限定的,但是你的编程语言可能增加它自己的一些规则,去限定堆在应用程序中的范围。
体系架构和操作系统是使用虚拟地址的,然后由处理器翻译到实际的物理地址中,还有页面错误等等。
它们记录那个页面属于那个应用程序。不过你不用关心这些,
因为你仅仅在你的编程语言中分配和释放内存,和一些错误检查(出现分配失败和释放失败的原因)。

它们的大小由什么决定?

依旧,依赖于语言,编译器,操作系统和架构。
栈通常提前分配好了,因为栈必须是连续的内存块。
语言的编译器或者操作系统决定它的大小。不要在栈上存储大块数据,这样可以保证有足够的空间不会溢出
除非出现了无限递归的情况(额,栈溢出了)或者其它不常见了编程决议。

堆是任何可以动态分配的内存的统称。
这要看你怎么看待它了,它的大小是变动的。
在现代处理器中和操作系统的工作方式是高度抽象的,
因此你在正常情况下不需要担心它实际的大小,除非你必须要使用你还没有分配的内存或者已经释放了的内存。

哪个更快一些?

栈更快因为所有的空闲内存都是连续的,因此不需要对空闲内存块通过列表来维护。
只是一个简单的指向当前栈顶的指针。
编译器通常用一个专门的、快速的寄存器来实现。
更重要的一点事是,随后的栈上操作通常集中在一个内存块的附近,这样的话有利于处理器的高速访问(译者注:局部性原理)。

答案四

你问题的答案是依赖于实现的,根据不同的编译器和处理器架构而不同。下面简单的解释一下:

  1. 栈和堆都是用来从底层操作系统中获取内存的。
  2. 在多线程环境下每一个线程都可以有他自己完全的独立的栈,但是他们共享堆。并行存取被堆控制而不是栈。

  1. 堆包含一个链表来维护已用和空闲的内存块。
    在堆上新分配(用 new 或者 malloc)内存是从空闲的内存块中找到一些满足要求的合适块。
    这个操作会更新堆中的块链表。这些元信息也存储在堆上,经常在每个块的头部一个很小区域。
  2. 堆的增加通常从低地址向高地址扩展。因此你可以认为堆随着内存分配而不断的增加大小。
    如果申请的内存大小很小的话,通常从底层操作系统中得到比申请大小要多的内存。
  3. 申请和释放许多小的块可能会产生如下状态:在已用块之间存在很多小的空闲块。
    进而申请大块内存失败,虽然空闲块的总和足够,但是空闲的小块是零散的,不能满足申请的大小。这叫做“堆碎片”。
  4. 当旁边有空闲块的已用块被释放时,新的空闲块可能会与相邻的空闲块合并为一个大的空闲块,这样可以有效的减少“堆碎片”的产生。

Heap

  1. 栈经常与 sp 寄存器(译者注:”stack pointer”,了解汇编的朋友应该都知道)一起工作,最初 sp 指向栈顶(栈的高地址)。
  2. CPU 用 push 指令来将数据压栈,用 pop 指令来弹栈。

当用 push 压栈时,sp 值减少(向低地址扩展)。当用 pop 弹栈时,sp 值增大。存储和获取数据都是 CPU 寄存器的值。
3. 当函数被调用时,CPU使用特定的指令把当前的 IP (译者注:“instruction pointer”,是一个寄存器,用来记录 CPU 指令的位置)压栈。
即执行代码的地址。CPU 接下来将调用函数地址赋给 IP ,进行调用。当函数返回时,旧的 IP 被弹栈,CPU 继续去函数调用之前的代码。
4. 当进入函数时,sp 向下扩展,扩展到确保为函数的局部变量留足够大小的空间。
如果函数中有一个 32-bit 的局部变量会在栈中留够四字节的空间。当函数返回时,sp 通过返回原来的位置来释放空间。
5. 如果函数有参数的话,在函数调用之前,会将参数压栈。函数中的代码通过 sp 的当前位置来定位参数并访问它们。
6. 函数嵌套调用和使用魔法一样,每一次新调用的函数都会分配函数参数,
返回值地址、局部变量空间、嵌套调用的活动记录都要被压入栈中。函数返回时,按照正确方式的撤销。
7. 栈要受到内存块的限制,不断的函数嵌套/为局部变量分配太多的空间,可能会导致栈溢出。
当栈中的内存区域都已经被使用完之后继续向下写(低地址),会触发一个 CPU 异常。
这个异常接下会通过语言的运行时转成各种类型的栈溢出异常。

Stack

函数的分配可以用堆来代替栈吗?

不可以的,函数的活动记录(即局部或者自动变量)被分配在栈上, 这样做不但存储了这些变量,而且可以用来嵌套函数的追踪。

堆的管理依赖于运行时环境,C 使用 malloc ,C++ 使用 new ,但是很多语言有垃圾回收机制。

栈是更低层次的特性与处理器架构紧密的结合到一起。
当堆不够时可以扩展空间,这不难做到,因为可以有库函数可以调用。
但是,扩展栈通常来说是不可能的,因为在栈溢出的时候,执行线程就被操作系统关闭了,这已经太晚了。

1.什么是堆和栈,它们在哪儿?

Git中从远程的分支获取最新的版本到本地有这样2个命令:

git fetch

相当于是从远程获取最新版本到本地,不会自动merge

git fetch origin master
git log -p master..origin/master
git merge origin/master

以上命令的含义:

  • 首先从远程的origin的master主分支下载最新的版本到origin/master分支上
  • 然后比较本地的master分支和origin/master分支的差别
  • 最后进行合并

上述过程其实可以用以下更清晰的方式来进行:

git fetch origin master:tmp
git diff tmp 
git merge tmp

从远程获取最新的版本到本地的test分支上,之后再进行比较合并

git pull

相当于是从远程获取最新版本并merge到本地

git pull origin master

上述命令其实相当于

git fetch 
git merge

在实际使用中,git fetch更安全一些
因为在merge前,我们可以查看更新情况,然后再决定是否合并

  1. Git 少用 Pull 多用 Fetch 和 Merge
  2. git fetch和git pull之间的区别
  3. 真正理解 git fetch, git pull 以及 FETCH_HEAD

  1. Scott Chacon’s book: Pro Git
  2. Git详解之一 Git起步
  3. Git详解之二 Git基础
  4. Git详解之三 Git分支
  5. Git详解之四 服务器上的Git
  6. Git详解之五 分布式Git
  7. Git详解之六 Git工具
  8. Git详解之七 自定义Git
  9. Git详解之八 Git与其他系统
  10. Git详解之九 Git内部原理

##

  1. Git 基本原理与常用命令
  2. 高质量的Git中文教程
  3. Git远程操作详解