编译原理学习一去除代码中的注释

前言开始学习编译原理了耶~关于编译原理的所有练习,按照老规矩,还是用我最喜欢的C#语言来实现,运行在.NetCore平台上~关于这个系列的所有代码已经上传到github了,项目主页: https://github.com/Deali-Axy/CompilerConstructionLearning本次题目对C或C++等高级程序设计语言编写的源程序中的//注释和/…/注释进行删除,保留删除后的源程序。要求以文件形式进行保存。思路分析程序主要功能就是消除已经编写好的源程序中的注释。在源程序中注释有两种形式,一种是单行注释,用“//”表示,另一种是多行注释,用“/…/”表示。针对这两种形式,程序中用了if..else..语句加以判断,并做出相应的处理。在这里还有可能出现另一种情况,上述两种注释符号可能出现在引号中,出现在引号中的注释符号并没有注释功能,因此在引号中出现的注释符号不应该被消除。所以,这次编写的程序将要分三种情况分析。第一种情况,单行注释:if (ch != temp){ // 这里就是单行注释 ofile.put(ch); ch = ifile.get();}或者 if (ch != temp){ /* 这里就是单行注释 */ ofile.put(ch); ch = ifile.get();}第二种情况,块注释:if (ifile.fail() || ofile.fail()){ cerr << "open file fail\n"; return EXIT_FAILURE; /*返回值EXIT_FAILURE(在cstdlib库中定义),用于向操作系统报* 告打开文件失败*/}第三种情况,行后注释:ifile.close(); // 关闭文件ofile.close();cout << "/////*////ret/rtr////";system("pause");return 0;还有一个关键的注意点可以看到这一行 cout << "/////*////ret/rtr////";这个字符串用双引号包起来的代码中有很多斜杠,所以要避免将这些斜杠识别为注释。这里我用的方法是在处理注释前先把包含注释符号的字符串替换掉,等注释删除之后,再添加回去。 实现代码注释写得很详细啦,配合上面的思路分析,我就不再继续分析代码了~ var sReader = new StreamReader(filePath);var newSource = "";var inBlock = false;var replaceFlag = false;var tempLine = ""; // 用于保存被替换的特殊行代码while (!sReader.EndOfStream){ var line = sReader.ReadLine(); if (line.Length == 0) continue; // 去除空行 var quotationPattern = "^(.*?)\".*//.*\""; var quotationResult = Regex.Match(line, quotationPattern); if (quotationResult.Success) { System.Console.WriteLine("替换特殊代码,双引号中包裹注释斜杠"); tempLine = quotationResult.Groups[0].Value; replaceFlag = true; line = Regex.Replace(line, quotationPattern, REPLACEMENT); } // 单行注释 if (line.Trim().StartsWith(@"//")) continue; if (line.Trim().StartsWith(@"/*") && line.EndsWith(@"*/")) continue; // 注释块 if (Regex.Match(line.Trim(), @"^/\*").Success) inBlock = true; if (Regex.Match(line.Trim(), @"\*/$").Success) { inBlock = false; continue; } // 行后注释 // 使用非贪婪模式(.+?)匹配第一个// var pattern = @"^(.*?)//(.*)"; // var pattern = @"[^(.*?)//(.*)]|[^(.*?)/\*(.*)\*/]"; var result = Regex.Match(line, pattern); if (result.Success) { System.Console.WriteLine("发现行后注释:{0}", result.Groups[2]); line = result.Groups[1].Value; } // 还原被替换的代码 if (replaceFlag) { System.Console.WriteLine("还原特殊代码"); line = line.Replace(REPLACEMENT, tempLine); replaceFlag = false; } if (inBlock) continue; newSource += line + Environment.NewLine;}var outputPath = "output/exp1.src";System.Console.WriteLine("去除注释完成,创建新文件。");using (var sWriter = new StreamWriter(outputPath)){ sWriter.Write(newSource);}System.Console.WriteLine("操作完成!文件路径:{0}", outputPath);结果测试源文件#include <iostream>#include <fstream>#include <iomanip>#include <cstdlib>using namespace std;int main(){ cout << '/'; ifstream ifile; //建立文件流对象 ofstream ofile; ifile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.cpp"); //打开F盘根目录下的fileIn.txt文件 ofile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.obj"); if (ifile.fail() || ofile.fail()) { //测试打开操作是否成功 cerr << "open file fail\n"; return EXIT_FAILURE; /*返回值EXIT_FAILURE(在cstdlib库中定义),用于向操作系统报* 告打开文件失败*/ } char ch; ch = ifile.get(); //进行读写操作 while (!ifile.eof()) { if (ch == 34) { //双引号中若出现“//”,双引号中的字符不消除 char temp = ch; //第一个双引号 ofile.put(ch); ch = ifile.get(); while (!ifile.eof()) { if (ch != temp) { //寻找下一个双引号 ofile.put(ch); ch = ifile.get(); } else { ofile.put(ch); break; } } ch = ifile.get(); continue; //双引号情况结束,重新新一轮判断 } if (ch == 47) { //出现第一个斜杠 char temp2 = ch; ch = ifile.get(); if (ch == 47) { //单行注释情况 ch = ifile.get(); while (!(ch == '\n')) ch = ifile.get(); } else if (ch == '*') { //多行注释情况 while (1) { ch = ifile.get(); while (!(ch == '*')) ch = ifile.get(); ch = ifile.get(); if (ch == 47) break; } ch = ifile.get(); } else { ofile.put(temp2); //temp2保存第一个斜杠,当上述两种情况都没有时,将此斜杠输出 } //ch = ifile.get(); } //cout << ch << endl; ofile.put(ch); //将字符写入文件流对象中 ch = ifile.get(); //从输入文件对象流中读取一个字符 } ifile.close(); //关闭文件 ofile.close(); cout << "/////*////ret/rtr////"; system("pause"); return 0;}处理后的结果#include <iostream>#include <fstream>#include <iomanip>#include <cstdlib>using namespace std;int main(){ cout << '/'; ifstream ifile; ofstream ofile; ifile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.cpp"); ofile.open("f:\\上机实验题\\C++\\ConsoleApplication2\\ConsoleApplication2\\源.obj"); if (ifile.fail() || ofile.fail()) { cerr << "open file fail\n"; return EXIT_FAILURE; } char ch; ch = ifile.get(); while (!ifile.eof()) { if (ch == 34) { char temp = ch; ofile.put(ch); ch = ifile.get(); while (!ifile.eof()) { if (ch != temp) { ofile.put(ch); ch = ifile.get(); } else { ofile.put(ch); break; } } ch = ifile.get(); continue; } if (ch == 47) { char temp2 = ch; ch = ifile.get(); if (ch == 47) { ch = ifile.get(); while (!(ch == '\n')) ch = ifile.get(); } else if (ch == '*') { while (1) { ch = ifile.get(); while (!(ch == '*')) ch = ifile.get(); ch = ifile.get(); if (ch == 47) break; } ch = ifile.get(); } else { ofile.put(temp2); } } ofile.put(ch); ch = ifile.get(); } ifile.close(); ofile.close(); cout << "/////*////ret/rtr////"; system("pause"); return 0;}完整代码https://github.com/Deali-Axy/CompilerConstructionLearning/blob/master/code/exp/exp1/Exp1.cs ...

May 11, 2019 · 3 min · jiezi

初学C选择哪个编译器比较合适为什么

C/C++开发环境,下面说说自己的一些看法,将日常开发中身边人经常使用的环境罗列出来,如果你有不同意见,欢迎留言讨论。最后,如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。Windows平台: 1)VisualStudio系列 宇宙第一IDE,不是吹出来的,配合“番茄插件(Visual Assist)”,写起来爽到爆,谁用谁知道。VisualStudio2013及之后的版本对C++ 11,17等新标准也支持比较好,对于VC6这种老古董,还是早点扔掉吧,千万别用VC6,千万别用VC6,千万别用VC6,重要的事情说三遍。 2)CodeBlocks 当然,和VS相比,肯定是不在同一个重量级上,不过这家伙最大的好处就在于其轻巧方便,安装包也不算很大。对于性能较差的电脑也许是一个不错的选择,而且也支持C++ 11标准,自带智能提示,对于新手学习,完全足矣。而且跨平台支持,完全免费,不用你再去百度各种注册码。 Linux平台: 1)gcc/g++、vim 不懂makefile的C/C++程序猿不是合格的工程师。会不会写Makefile,也许真的是衡量一个人水平的真正标准了,学会了它,你不再是windows下的那个只会点点按钮来完成编译,链接的人了。当然Vim也可以配置的和IDE一般强大的,这需要你有足够强的耐心,下面是一张我在Windows下自己配置使用的gvim截图。如有需要vim配置文件,可私信或楼下留言(PS:已经将Vim的杀手锏插件YouCompleteMe集成进去)。 2)JetBrains CLion JetBrains CLion 是一个收费的、强大的跨平台 C/C++ IDE。它是一个完全整合的 C/C++ 程序开发环境,并提供 cmake 项目模型、一个嵌入式终端窗口和一个主要以键盘操作的编码环境。它还提供了一个智能而现代化的编辑器,内置Git支持,VIM插件,C/C++智能提示等等多个神器。 3)Qt Creator 在Linux平台开发,这款IDE也是很常见的吧,尤其对一些做UI开发的coder来说。它用于创建连接设备、用户界面和应用程序。Qt Creator 可以让用户比应用的编码做到更多的创新。可以用来创建移动和桌面应用程序,也可以连接到嵌入式设备。首先明确你想问的是编译器还是编辑器/IDE? 也和你使用的平台有关。 编译器有mingw、gcc和clang等等; 编辑器有vim、sublime text、vs code等; IDE有visual studio、clion等。

May 9, 2019 · 1 min · jiezi

PHP源码学习20190321-AST

【PHP源码学习】2019-03-21 ASTbaiyan 全部视频:https://segmentfault.com/a/11... 原视频地址:http://replay.xesv5.com/ll/24... 引入抽象语法树(AST)是PHP7中新引入的,在许多其他语言中早已有实现。为什么要有AST这种东西呢?因为文本类型的数据对计算机并不友好,需要将其转换成数据结构,才能更加方便地对词法分析得到的token进行操作。例:a = b + c,怎么用抽象语法树来表达? 那么使用中序遍历就可以得到上述表达式。拓展:对于树的中序遍历,有递归与非递归两种方式: 递归中序遍历很简单,递归访问左子树、根节点、右子树即可非递归中序遍历:借助栈 - 碰到等号压栈,然后往左子树走- 将a压栈,a没有左右子树,a出栈(a)- 等号出栈(a =)- 将它的右子树加号压栈- 加号有左子树,将b压栈- b没有左右子树,b出栈(a = b)- 加号出栈(a = b +)- 加号有右子树c,将c压栈- c没有左右子树,c出栈(a = b + c)树的层序遍历:借助队列,此处不展开回到AST, 那么如果在PHP中,让你去实现一个AST,你会怎么实现?AST结点的设计第一版:struct Tree { char type //结点类型,表示是运算符还是操作数等 Tree *left //左孩子 Tree *right //右孩子}存在的问题:因为不是所有表达式都是二元表达式,故不可以全部都用二叉树表示(如foreach循环中foreach($a as $k => $v)至少需3个结点来表示$a/$k/$v等词法单元),故需要在此基础上做一些扩展。第二版:struct Tree { char type //结点类型,表示是运算符还是操作数等 int children //有多少个孩子 Tree child[] //用一个数组存储所有孩子}PHP中AST的重要结构与概念struct _zend_ast { zend_ast_kind kind; /* 结点类型,相当于我们上面的type */ zend_ast_attr attr; /* 先忽略 */ uint32_t lineno; /* 行号(进行语法分析的时候需要记录代码所在行号) */ zend_ast *child[1]; /* 柔性数组,存储孩子结点*/};这样一看,好像并没有存储有多少个孩子的字段。注意这个kind字段,它是zend_ast_kind类型,看下这个类型的定义:typedef uint16_t zend_ast_kind;它是一个uint16类型,足以存储所有类型了,那么具体有哪些类型呢?在PHP中,AST的结点是按照如下代码所示分类存储的,这些分类用枚举类型来表示:enum _zend_ast_kind { /* special nodes 特殊类型结点*/ ZEND_AST_ZVAL = 1 << ZEND_AST_SPECIAL_SHIFT, (1 << 6 = 64) ZEND_AST_ZNODE, (65) /* declaration nodes 定义类型结点*/ ZEND_AST_FUNC_DECL, ZEND_AST_CLOSURE, ZEND_AST_METHOD, ZEND_AST_CLASS, /* list nodes LIST类型结点*/ ZEND_AST_ARG_LIST = 1 << ZEND_AST_IS_LIST_SHIFT,(1 << 7 = 128) ZEND_AST_ARRAY, ZEND_AST_ENCAPS_LIST, ZEND_AST_EXPR_LIST, ZEND_AST_STMT_LIST, ZEND_AST_IF, ZEND_AST_SWITCH_LIST, ZEND_AST_CATCH_LIST, ZEND_AST_PARAM_LIST, ZEND_AST_CLOSURE_USES, ZEND_AST_PROP_DECL, ZEND_AST_CONST_DECL, ZEND_AST_CLASS_CONST_DECL, ZEND_AST_NAME_LIST, ZEND_AST_TRAIT_ADAPTATIONS, ZEND_AST_USE, /* 0 child nodes 0个孩子结点*/ ZEND_AST_MAGIC_CONST = 0 << ZEND_AST_NUM_CHILDREN_SHIFT,(0 << 8 = 0) ZEND_AST_TYPE, /* 1 child node 1个孩子结点*/ ZEND_AST_VAR = 1 << ZEND_AST_NUM_CHILDREN_SHIFT,(1 << 8 = 256) ZEND_AST_CONST, ZEND_AST_UNPACK, ZEND_AST_UNARY_PLUS, ZEND_AST_UNARY_MINUS, ZEND_AST_CAST, ZEND_AST_EMPTY, ZEND_AST_ISSET, ZEND_AST_SILENCE, ZEND_AST_SHELL_EXEC, ZEND_AST_CLONE, ZEND_AST_EXIT, ZEND_AST_PRINT, ZEND_AST_INCLUDE_OR_EVAL, ZEND_AST_UNARY_OP, ZEND_AST_PRE_INC, ZEND_AST_PRE_DEC, ZEND_AST_POST_INC, ZEND_AST_POST_DEC, ZEND_AST_YIELD_FROM, ZEND_AST_GLOBAL, ZEND_AST_UNSET, ZEND_AST_RETURN, ZEND_AST_LABEL, ZEND_AST_REF, ZEND_AST_HALT_COMPILER, ZEND_AST_ECHO, ZEND_AST_THROW, ZEND_AST_GOTO, ZEND_AST_BREAK, ZEND_AST_CONTINUE, /* 2 child nodes 2个孩子结点*/ ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT,(2 << 8 = 512) ZEND_AST_PROP, ZEND_AST_STATIC_PROP, ZEND_AST_CALL, ZEND_AST_CLASS_CONST, ZEND_AST_ASSIGN, ZEND_AST_ASSIGN_REF, ZEND_AST_ASSIGN_OP, ZEND_AST_BINARY_OP, ZEND_AST_GREATER, ZEND_AST_GREATER_EQUAL, ZEND_AST_AND, ZEND_AST_OR, ZEND_AST_ARRAY_ELEM, ZEND_AST_NEW, ZEND_AST_INSTANCEOF, ZEND_AST_YIELD, ZEND_AST_COALESCE, ZEND_AST_STATIC, ZEND_AST_WHILE, ZEND_AST_DO_WHILE, ZEND_AST_IF_ELEM, ZEND_AST_SWITCH, ZEND_AST_SWITCH_CASE, ZEND_AST_DECLARE, ZEND_AST_USE_TRAIT, ZEND_AST_TRAIT_PRECEDENCE, ZEND_AST_METHOD_REFERENCE, ZEND_AST_NAMESPACE, ZEND_AST_USE_ELEM, ZEND_AST_TRAIT_ALIAS, ZEND_AST_GROUP_USE, /* 3 child nodes 3个孩子结点*/ ZEND_AST_METHOD_CALL = 3 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_STATIC_CALL, ZEND_AST_CONDITIONAL, ZEND_AST_TRY, ZEND_AST_CATCH, ZEND_AST_PARAM, ZEND_AST_PROP_ELEM, ZEND_AST_CONST_ELEM, /* 4 child nodes 4个孩子结点*/ ZEND_AST_FOR = 4 << ZEND_AST_NUM_CHILDREN_SHIFT, ZEND_AST_FOREACH,};先忽略上面特殊节点、定义结点和LIST类型结点这几个类型,主要关注下面0个子结点、1个子结点的类型,这样,我们根据zend_ast结构体中存储的kind数值,再对照这个类型表,就可以知道它有几个子结点了。我们再看LIST类型,它是怎么存储子结点数量的呢?会转成一个专门的结构体用来存储LIST类型的结点:/* Same as zend_ast, but with children count, which is updated dynamically *//*与zend_ast结点的功能相同但是多了一个子结点的计数,它会被动态地更新*/typedef struct _zend_ast_list { zend_ast_kind kind; zend_ast_attr attr; uint32_t lineno; uint32_t children; zend_ast *child[1];} zend_ast_list;我们看这个结构体,除了uint32_t类型的children子结点计数字段,其余与我们上述zend_ast结构体一摸一样。这样,在基本的zend_ast结构体中,kind字段只需要存一个数字,代表它是什么类型,就可以直接得出是LIST类型(孩子结点个数存在对应的zend_ast_list结构体中)有0个、1个、2个孩子结点等等。再关注特殊类型中的ZEND_AST_ZVAL类型,它代表AST中结点变量或者常量的值(如变量$a的值就为字符串"a",常量1的值为1,均存在这个zval中,下文会讲)/* Lineno is stored in val.u2.lineno *//* Lineno 字段存储在zval中的 val.u2.lineno中 */typedef struct _zend_ast_zval { zend_ast_kind kind; zend_ast_attr attr; zval val;} zend_ast_zval;最后剩下的就是定义类型,包括类、函数等定义,会转成如下结构存储定义类型的信息:/* Separate structure for function and class declaration, as they need extra information. *//* 为函数和类设计的特殊结构,它们需要额外的描述信息 */typedef struct _zend_ast_decl { zend_ast_kind kind; zend_ast_attr attr; /* Unused - for structure compatibility */ uint32_t start_lineno; //类和函数是一个区间,所以记录开始行号和结束行号 uint32_t end_lineno; uint32_t flags; unsigned char *lex_pos; zend_string *doc_comment; zend_string *name; zend_ast *child[4];} zend_ast_decl;PHP中AST实现示例简单的赋值与表达式示例我们看下面一段PHP代码,看下它的AST结构是什么样的:<?php$a = 1;$b = $a + 2;利用gdb调试这段代码,并在zend_compile处打一个断点。这里会进行词法分析和语法分析(注意词法分析和语法分析是同时执行的,解析出一个token就开始进行语法分析,而不是串行执行的),并查看compiler_globals.ast字段,这个字段就是生成的抽象语法树指针了,我们打印它的内容: ...

May 7, 2019 · 2 min · jiezi

递归和尾递归的运行流程解释

递归和尾递归的运行流程解释递归定义递归(英语:recursion)在计算机科学中是指一种通过重复将问题分解为同类的子问题而解决问题的方法。[1] 递归式方法可以被用于解决很多的计算机科学问题,因此它是计算机科学中十分重要的一个概念。[2] 绝大多数编程语言支持函数的自调用,在这些语言中函数可以通过调用自身来进行递归。计算理论可以证明递归的作用可以完全取代循环,因此有很多在函数编程语言(如Scheme)中用递归来取代循环的例子。(摘自维基百科) 尾递归定义在计算机学里,尾调用是指一个函数里的最后一个动作是返回一个函数的调用结果的情形,即最后一步新调用的返回值直接被当前函数的返回结果。[1]此时,该尾部调用位置被称为尾位置。尾调用中有一种重要而特殊的情形叫做尾递归。经过适当处理,尾递归形式的函数的运行效率可以被极大地优化。[1]尾调用原则上都可以通过简化函数调用栈的结构而获得性能优化(称为“尾调用消除”),但是优化尾调用是否方便可行取决于运行环境对此类优化的支持程度如何。(摘自维基百科) 前提知识递归我将它分为两个过程,一个我将它称为递归,另一个我将它称为回溯. 递归的函数的运行主要有这两个流程,递归的进入,回溯的退出,这两个过程的分界是以递归出口为分界的.递归的实现形式是使用栈,递归函数的进入(递归)类似与压栈,递归函数的退出(回溯)类似于出栈.递归样例和解释【编程题】幂运算三(递归函数)题目ID:1137【问题描述】 求x^n。【输入形式】一行2个数,第一个整数表示x,第二个大于等于零的整数表示n,二数之间用空格分隔。【输出形式】一行一个整数,表示x的n次方【样例输入】2 3【样例输出】8 【样例说明】2的3次方结果为8【评分标准】5组测试用例,每组2分,共计10分 【测试用例】1)输入:2 3输出:82)输入:3 5输出:243 3)输入:-17 4输出:83521 4)输入:22 0输出:1 5)输入:-1287 0输出:1 //普通递归#include<stdio.h>long my_pow1(long x,int n){ if(n==0) return 1; //递归出口 return x*(my_pow1(x,--n)); //除了调用自身外还乘多了个x,即一般的递归}int main(){ long x; int n; scanf("%ld%d",&x,&n); printf("%ld\n",my_pow1(x,n)); return 0;} 运行图解解释:普通的递归过程是在一个函数中,结果依靠自身的调用来得出,例如求幂运算,pow(2,3)代表求2的3次方,由于pow(2,3)未知,我们可以把它分解成2*pow(2,2),pow(2,2)也未知,又可分解成2*pow(2,1),以此类推,直到pow(2,0)可知(if中定义0时返回1),即pow(2,0)返回值是1.在这个递归过程中,pow函数的建立就是一个个压栈的过程我把它称为函数栈 压栈压入所以函数后,直到最后一个,可以获得最后一个函数的返回值,由这个返回值可以依次推出栈内所有函数的返回值(回溯),即退栈,pow(2,0)返回1,推的pow(2,1)返回2*pow(2,0),即2*1=2,pow(2,2)返回2*pow(2,1),即2*2=4,直到退到栈内最后一个函数pow(2,3),可获得pow(2,3)的返回值为2*pow(2,2)即8; 尾递归样例和解释【编程题】吃糖(尾递归函数) 题目ID:1135【问题描述】小樱是个爱吃糖的女孩, 哥哥送了她n(1<=n<=30)颗糖,怎么吃?一天吃1颗;一天吃2颗。嗯,那就每天吃一颗或两颗吧。1颗糖,肯定只有(1)一种吃法;2颗糖,有(1,1)和(2)两种吃法;3颗糖,有(1,1,1)、(1,2)和(2,1)三种吃法。注 (2,1)表示第一天吃2颗,第二天吃1颗。*你能帮小樱算出,吃完这n颗糖,有多少种吃法吗?请编写一个尾递归函数来解决此问题【输入形式】 【测试用例】1)输入:1输出:result=1 2)输入:4输出:result=5 3)输入:15输出:result=987 4)输入:20输出:result=10946 5)输入:30输出:result=1346269 实际上这道题是一个斐波那契数列的变体,可用尾递归函数解决 //尾递归#include <stdio.h>int ci(int n,int pre,int next){ int sum; if (n==1){ //递归出口(递归和回溯的分界点) return pre; } return ci(n-1,next,pre+next); //除了调用自身外没有其他操作即为尾递归}int main(){ int n; int sum; scanf ("%d",&n); printf ("result=%d",ci(n,1,2)); return 0;}运行图解 ...

May 7, 2019 · 1 min · jiezi

MaskRCNNBenchmarkPytorch版本训练自己的数据以及避坑指南

一、安装 地址:MaskRCNN-Benchmark(Pytorch版本) 首先要阅读官网说明的环境要求,千万不要一股脑直接安装,不然后面程序很有可能会报错!!! PyTorch 1.0 from a nightly release. It will not work with 1.0 nor 1.0.1. Installation instructions can be found in https://pytorch.org/get-start...torchvision from mastercocoapiyacsmatplotlibGCC >= 4.9OpenCV# first, make sure that your conda is setup properly with the right environment# for that, check that `which conda`, `which pip` and `which python` points to the# right path. From a clean conda env, this is what you need to doconda create --name maskrcnn_benchmarkconda activate maskrcnn_benchmark# this installs the right pip and dependencies for the fresh pythonconda install ipython# maskrcnn_benchmark and coco api dependenciespip install ninja yacs cython matplotlib tqdm opencv-python# follow PyTorch installation in https://pytorch.org/get-started/locally/# we give the instructions for CUDA 9.0conda install -c pytorch pytorch-nightly torchvision cudatoolkit=9.0export INSTALL_DIR=$PWD# install pycocotoolscd $INSTALL_DIRgit clone https://github.com/cocodataset/cocoapi.gitcd cocoapi/PythonAPIpython setup.py build_ext install# install apexcd $INSTALL_DIRgit clone https://github.com/NVIDIA/apex.gitcd apexpython setup.py install --cuda_ext --cpp_ext# install PyTorch Detectioncd $INSTALL_DIRgit clone https://github.com/facebookresearch/maskrcnn-benchmark.gitcd maskrcnn-benchmark# the following will install the lib with# symbolic links, so that you can modify# the files if you want and won't need to# re-build itpython setup.py build developunset INSTALL_DIR# or if you are on macOS# MACOSX_DEPLOYMENT_TARGET=10.9 CC=clang CXX=clang++ python setup.py build develop一定要按上面的说明一步一步来,千万别省略,不然后面程序很有可能会报错!!! ...

May 6, 2019 · 6 min · jiezi

Swoole源码研究深入理解Swoole协程实现

作者:李乐 本文基于Swoole-4.3.2和PHP-7.1.0版本 Swoole协程简介 Swoole4为PHP语言提供了强大的CSP协程编程模式,用户可以通过go函数创建一个协程,以达到并发执行的效果,如下面代码所示: <?php//Co::sleep()是Swoole提供的API,并不会阻塞当前进程,只会阻塞协程触发协程切换。go(function (){ Co::sleep(1); echo "a";});go(function (){ Co::sleep(2); echo "b";});echo "c";//输出结果:cab//程序总执行时间2秒 其实在Swoole4之前就实现了多协程编程模式,在协程创建、切换以及结束的时候,相应的操作php栈即可(创建、切换以及回收php栈)。 此时的协程实现无法完美的支持php语法,其根本原因在于没有保存c栈信息。(vm内部或者某些扩展提供的API是通过c函数实现的,调用这些函数时如果发生协程切换,c栈该如何处理?) Swoole4新增了c栈的管理,在协程创建、切换以及结束的同时会伴随着c栈的创建、切换以及回收。 Swoole4协程实现方案如下图所示: 其中: API层是提供给用户使用的协程相关函数,比如go()函数用于创建协程;Co::yield()使得当前协程让出CPU;Co::resume()可恢复某个协程执行;Swoole4协程需要同时管理c栈与php栈,Coroutine用于管理c栈,PHPCoroutine用于管理php栈;其中Coroutine(),yield(),resume()实现了c栈的创建以及换入换出;create_func(),on_yield(),on_resume()实现了php栈的创建以及换入换出;Swoole4在管理c栈时,用到了 boost.context库,make_fcontext()和jump_fcontext()函数均使用汇编语言编写,实现了c栈上下文的创建以及切换;Swoole4对boost.context进行了简单封装,即Context层,Context(),SwapIn()以及SwapOut()对应c栈的创建以及换入换出。 深入理解C栈 函数是对代码的封装,对外暴露的只是一组指定的参数和一个可选的返回值;假设函数P调用函数Q,Q执行后返回函数P,实现该函数调用需要考虑以下三点: 指令跳转:进入函数Q的时候,程序计数器必须被设置为Q的代码的起始地址;在返回时,程序计数器需要设置为P中调用Q后面那条指令的地址;数据传递:P能够向Q提供一个或多个参数,Q能够向P返回一个值;内存分配与释放:Q开始执行时,可能需要为局部变量分配内存空间,而在返回前,又需要释放这些内存空间; 大多数语言的函数调用都采用了栈结构实现,函数的调用与返回即对应的是一系列的入栈与出栈操作,我们通常称之为函数栈帧(stack frame)。示意图如下: 上面提到的程序计数器即寄存器%rip,另外还有两个寄存器需要重点关注:%rbp指向栈帧底部,%rsp指向栈帧顶部。 下面将通过具体的代码事例,为读者讲解函数栈帧。c代码与汇编代码如下: int add(int x, int y){ int a, b; a = 10; b = 5; return x+y;}int main(){ int sum = add(1,2);}main: pushq %rbp movq %rsp, %rbp subq $16, %rsp movl $2, %esi movl $1, %edi call add movl %eax, -4(%rbp) leave retadd: pushq %rbp movq %rsp, %rbp movl %edi, -20(%rbp) movl %esi, -24(%rbp) movl $10, -4(%rbp) movl $5, -8(%rbp) movl -24(%rbp), %eax movl -20(%rbp), %edx addl %edx, %eax popq %rbp ret 分析汇编代码: ...

May 6, 2019 · 6 min · jiezi

求回文数的三种算法的c语言描述

c语言求回文数的三种算法的描述题目描述注意:(这些回文数都没有前导0)1位的回文数有0,1,2,3,4,5,6,7,8,9 共10个;2位的回文数有11,22,33,44,55,66,77,88,99 共9个;* 请问:n位的回文数有多少个?请编写一个递归函数来解决此问题!!! 【输入形式】一行一个正整数,代表多少位【输出形式】一行一个正整数,代表回文诗的个数【样例输入】2【样例输出】9 输入:3输出:90 输入:5输出:900 **输入:10输出:90000** 输入:8输出:9000 输入:1输出:10 思路分析通过for循环读入这个数,通过/和%操作将这个数据逆转,然后再对比逆转后的数字是否和原数字相等 通过for循环读入这个数,每次取头位一个数字和末位一个数字,依次比较这两个数字是否相等,再去掉这两个数字,直到剩下一个数字(位数为奇数)或者剩下两个数字(位数为偶数) . 通过数学关系,直接判断位数,算出这个位数内的回文数个数; 例如:99899可以把它分为两半,取前面一半998,如果是回文数,其后面一半一定是与其相应位置对应,998为3位数字,除第一位(不包含前导0)故与后半对应的位置那个数有9种选择(1-9)外,其他位都与相应的位置有10种选择(0-9),例如第二位和倒数第二位(0-9)所以可以总结出来相同的位数,位数为奇数奇数其回文数有910^(n/2)个,注意n/2是整数,位数为偶数的为910^(n/2-1)个,所以5位数字的的回文数有91010=900个注意位数为1有10个(0-9),需要特殊处理代码描述第一种思路:#include <stdio.h>#include <math.h>int reverse(long int i,long int *terminate) //递归函数求数值的逆序{ if (i<=0){ //递归出口 return 1; } else{ *terminate*=10; //每次乘10升位数 *terminate+=i%10; //加上个位 reverse(i/10,terminate); //递归每次规模缩小 } return 1;}int main (){ int n; scanf ("%d",&n); //读入一个n,表示n位整数 long int i; int count=0; if (n==1){ //如果等于1,则有10个(0-9都是),特殊处理; printf ("10"); return 0; } for (i=pow(10,n-1);i<pow(10,n);i++){ //从第一个n位数开始(10^(n-1)),到(10^n)-1 long int terminate=0; //定义一个逆序目标数 reverse(i,&terminate); //把i和逆序目标数传入 if (terminate==i){ //逆序后还和原数相等,则可计数 count++; } } printf ("%d",count); //输出个数 return 0;}第二种思路:#include <stdio.h>#include <math.h>int judge(int i,int n){ int first,last; if (n<=1){ //规模减小,直到n为1(偶数)或者0 return 1; } else{ first=i/pow(10,n-1); //头位数字 last=i%10; //末位数字 if (first!=last){ //头位末尾不一样直接退出 return 0; } int tem=pow(10,n-1); judge(i%tem/10,n-2); //剔除头尾剩下中间,位数减二 }}int main (){ int n; scanf("%d",&n); if (1==n){ printf ("10"); return 0; } int i; int count=0; long long low=pow(10,n-1); //循环入口 long long high=pow(10,n); //循环出口 for (i=low;i<high;i++){ if ( judge(i,n)==1){ //判断i是否为回文,计数 count++; } } printf ("%d",count); return 0;}第三种思路:#include <stdio.h>#include <math.h>int main (){ int n; scanf ("%d",&n); int ji=9*pow(10,n/2),ou=9*pow(10,n/2-1); if (n==1){ printf ("10"); } else if (n==2){ printf ("%d",9); } else if (n%2==1){ printf ("%d",ji); } else if (n%2==0){ printf("%d",ou); } return 0;}额外疑问第一第二种方法当n=10的时候运算不出来,求解为何如此,是时间复杂度太高了吗?还是爆int了或者爆递归了?

April 30, 2019 · 1 min · jiezi

青少年大学生职场精英给你一个学编程的理由

《图解C++——轻松学编程》序计算机编程已经成为欧美学生必修课程之一,过几年当你再遇到这些歪果仁的时候,吓到你的不是他们的英文表达能力而是他们熟练的编程能力。目前在国内编程还只是程序员的工作,大多数非计算机从业人员没有接触过编程知识。随着信息传输5G时代的来临,科技发展的速度会像闪电一般。未来智能家电充斥楼宇,假如学生少年掌握编程技能就可以控制、改善智能家电的控制程序,也可以扩展兴趣爱好,像机器人AI编程、趣味小游戏开发都是有益身心的课外实践活动;校园内大学生在论文课题的研究中,算法模型需要进行大量的数据验证,编程可以很好的完成数据流控制和成果的论证;商业经营中大数据分析亦离不开程序软件的开发,尤其是互联网信息行业需要掌握用户大数据的获取方法和处理方式。在北上广,编程已经构成了职场精英的核心竞争优势,无可替代。如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 编程不仅是工作中才需要的生存技能,事实证明越早学编程越能激发人的创造力和锻炼人的思考能力。学习编程带来的益处包括但不限于:1、 强化抽象思维能力学英文可以和老外沟通,而学编程可以和机器沟通。在编程实现中,需要把抽象的问题转化为具体的解决步骤,好让没有判断力的电脑,了解需要完成哪些任务。抽象思维能力对青少年以后学好物理、化学大有裨益,抽象能力强可以轻松的把一个物理公式具化为物理运动,把一个化学实验抽象为化学反应式。2、发展逻辑推理能力好的程序员要能够把控全局且注重细节,编程必须去思考:如何把解决问题的代码合理的安排在整个程序中?让程序正确的处理数据输入、演算、直到输出结果。编程可以提高人脑对事务的逻辑分析能力,逻辑性极强的编程脑力劳动会让思维变得严谨有序,前后协同。学生少年如果拥有强大的逻辑能力可以轻松应对数学科目中的数学问题。3.培养专注力写出一行正确的代码,需要每个细节都准确无误。有趣的编程案例可以让人连续长时间的投入其中,毫无倦意。代码编辑过程中有时只是少敲了一个字母或落下一个符号,程序就会报错而无法运行。所以编写每一行代码都需要:手不离键,目不转睛,敏捷思考。编程中每一个功能的实现,在紧张过后都会换来成功的愉悦,这会激发出人更大的探索兴趣。所以程序员都是很专注很有自驱力的一群人,没酬劳也常常加班到深夜。编程让人如此优秀!

April 29, 2019 · 1 min · jiezi

PHP源码学习20190320-PHP词法分析

【PHP源码学习】2019-03-20 PHP词法分析baiyan 全部视频:https://segmentfault.com/a/11... 原视频地址:http://replay.xesv5.com/ll/24... 基本概念在PHP7中,当一个请求到来时,先加载对应的PHP代码,后进行词法分析和语法分析并生成抽象语法树(AST),然后进行深度优先遍历并生成opcodes,在zend虚拟机中执行这些opcode并返回执行结果。在PHP中,使用的词法分析器是Re2c,语法分析器是Bison。词法分析:词法分析阶段是编译过程的第一个阶段。这个阶段的任务是从左到右一个字符一个字符地读入源程序,即对构成源程序的字符流进行扫描然后根据构词规则识别单词(也称单词符号或符号,即token)。词法分析程序实现这个任务。词法分析程序可以使用Re2c、lex等工具自动生成。语法分析:语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”等等。语法分析程序判断源程序在结构上是否正确。其实进行词法分析和语法分析并生成某种数据结构的过程,就是一个解码的过程。之所以需要做这种从字符串到数据结构(AST)的转换,是因为编译器是无法直接操作“1+2”这样的字符串的。实际上,代码的本质根本就不是字符串,它本来就是一个具有复杂拓扑的数据结构,就像电路一样。“1+2”这个字符串只是对这种数据结构的一种“编码”,就像ZIP或者JPEG只是对它们压缩的数据的编码一样。这种编码可以方便你把代码存到磁盘上,方便你用文本编辑器来修改它们(对人友好,方便人们编写代码,但是对编译器不友好),然而你必须知道,文本并不是代码本身。所以从磁盘读取了文本之后,你必须先“解码”,才能方便地操作代码的数据结构。比如,如果上面的Java代码生成的AST节点叫node,你就可以用node.operator来访问加号ADD,用node.left来访问1,node.right来访问2。这是很方便的。对于程序语言,这种解码的动作就叫做parsing,用于解码的那段代码就叫做parser。关于语法分析与词法分析的具体概念解释,这篇文章写得较好:对 Parser 的误解我们先利用PHP内置函数token_get_all()来取出一段PHP代码的token:<?php$lan = '<?php $a = 1; echo $a';$tokens = token_get_all($lan);foreach ($tokens as $token) { if (is_array($token)) { echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')", PHP_EOL; }}打印结果为:Line 1: T_OPEN_TAG ('<?php ')Line 1: T_VARIABLE ('$a')Line 1: T_WHITESPACE (' ')Line 1: T_WHITESPACE (' ')Line 1: T_LNUMBER ('1')Line 1: T_WHITESPACE (' ')Line 1: T_ECHO ('echo')Line 1: T_WHITESPACE (' ')Line 1: T_VARIABLE ('$a')观察以上结果,可以看到取出来的token。如何取出token那么让我们你自己去设计一个算法,从一个字符串中识别并取出token,应该怎么做? 使用两个指针,一个标记开始位置,一个往后挪,然后回溯。(较麻烦)使用正则表达式进行匹配当用较简单的字符串匹配正则表达式的时候,可以用人眼很容易地看出来。但是如果用很复杂的字符串(成千上万行代码)去匹配一个正则,是相当麻烦并且非常慢的,编译原理中提出了这样一个概念用以解决这个问题:有穷状态机。有穷状态机:必须有一个起始状态,用一个箭头加圆圈表示;也得有一个结束,用两个圆圈表示。 如果满足某个条件,就会从一个状态跃迁到另一个状态,也用箭头来表示。例:观察下面这个正则表达式: ...

April 28, 2019 · 1 min · jiezi

链表初始化为何使用二级指针的解释指向指针的指针

引言在数据结构的学习过程中,有时候会遇到一些一时无法理解的问题,深究起来却是语言的底层的语法机制所限制. 就例如在链表的构建中,链表的初始化和销毁为何需要使用一个二级指针,而不是只需要传递一个指针就可以了,其问题的关键就在于c语言的参数传递的方式是值传递那么,这篇文章就来聊一聊在链表的初始化中一级指针的传递和二级指针的区别. 一级指针和二级指针的区别1.前提知识:c语言中参数传递的方式是值传递和地址传递值传递:传递的是实参的副本,即形参是一个新开辟的类型一样,里面的内容一样,地址不一样的一个变量,主函数和被被调用函数用的是不一样的内存空间地址传递:传递的是一个地址,实际上主函数和被调用函数共用的是同一块内存空间,被调用函数依然需要一个新的指针来保存起来这块地址2.传递一级指针:无法对原指针指向第地址进行修改一级指针实例: #include <stdio.h>#include <stdlib.h>#define MaxSize 100typedef int ElemType;typedef struct SingleNode{ ElemType data; struct SingleNode *next;}SingleNodeList,*Linklist;void LinkedListInit(SingleNodeList *head){//用一个指针head接收传入的地址 Linklist p; if((head=(SingleNodeList *)malloc(sizeof(SingleNodeList)))==NULL){ exit(1);//给head分配内存,改变了head指针指向的地址(注意这里的head只是LinkedListInit的head,不是主函数那个) } head->next=NULL;}//这个函数结束后head被销毁了,主函数的那个head不变;int LinkedList_PushFront(SingleNodeList *head,ElemType x){//2单链表头插入 SingleNodeList *q; if((q=(struct SingleNode *)malloc(sizeof (struct SingleNode)))==NULL){ exit(1); } q->data=x; q->next=head->next;//头节点的数据域与指针域赋值 head->next=q;//头节点加入链表 return 1;}int LinkedList_PopFront(SingleNodeList *head,ElemType *x){//3单链表头删除 SingleNodeList *p=head,*q; if(p->next==NULL){ printf("There is no data in the Linkedlist to delete.\n"); *x = -12345;//未成功删除则在x指向单元赋特定值 return 0; } p=head->next; q=p; head->next=p->next; *x=q->data; free(q); return *x;//请填写多行代码}int LinkedListGet_current(SingleNodeList *p,ElemType *x){//4取当前指针指数据 *x =p->data; return 1;}int LinkedListUpdata_current(SingleNodeList *p,ElemType x){//5修改当前指针数据 p->data=x; return 1;}int LinkedListShow(SingleNodeList *head){//6打印单链表 SingleNodeList *p=head; if(p->next==NULL){ printf("There is no data in the Linkedlist to print.\n"); return 0; } while(p->next!=NULL){ printf("%d ",p->next->data); p=p->next; } printf("\n"); return 1;}void LinkedListDestroy(SingleNodeList **head){//7释放链表 SingleNodeList *p=*head,*q; while(p!=NULL){ q=p; p=p->next; free(q); } *head=NULL;}int LinkedListLength(SingleNodeList *head){//8求单链表长度 SingleNodeList *p=head; int size=0; while(p->next!=NULL){ size++; p=p->next; } return size;}int main(){ SingleNodeList *head,*p; ElemType i,x; int switch_num; scanf("%d",&switch_num); switch(switch_num){ case 1: LinkedListInit(head); //传入指针变量head的地址 LinkedList_PushFront(head,1); LinkedList_PushFront(head,3); LinkedList_PushFront(head,2); LinkedListShow(head); break; } LinkedListDestroy(&head); return 0;}传递流程如图所示 ...

April 27, 2019 · 2 min · jiezi

Cc-如何简单上手

学习C++已经有俩个多月了,感觉C++学习就是先符合理论→上机练习→再总结→再练习的一个认识过程。一开始要具有一定的计算机理论基础知识,了解最基本的概念,具备了入门的条件,就可以开始编程的实践,从实践中可以发现问题需要加强计算机理论知识的不断总结。总之,C++学习就是一个不断进行代码练习和知识点总结的过程。以下就是我对整个C++体系囊括知识点的总结图,希望对大家有帮助。如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 在实际开发中,经常会遇到C++与C混用的情况,具体方法如下 C1.c文件是用C编写的C文件:? CPP1.cpp文件是用c++编写的C++文件 ? 编译后会出错,因为一个文件是c文件一个是cpp,两者又通过extern联系变量,所以出错 这时要使用extern"C"关键字 所以在C++文件中编译C文件需要使用extern "C"关键字,声明语法如下所示 extern "C" { 采用C语言实现的内容 } 在CPP1.cpp文件中: ? 这样就可以通过编译了

April 27, 2019 · 1 min · jiezi

C语言和C的区别是什么8个点通俗易懂的告诉你

有些初学的同学傻傻分不清其中的区别,下面我将详细的讲解C语言和C++的区别点。帮助大家尽快的理解。 1、关键字 蓝色标注为C语言关键字,C++继承了C语言的所有关键字,以下红色标注为C++中含有但C语言没有的关键字(根据c++98中提出C++包含63个关键字)如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 2、源文件区别 C语言文件后缀为.c,c++原文件名后缀为.cpp 如果在创建源文件时什么都不给,默认是.cpp 3.返回值不同 C语言中,如果一个函数没有指定返回值类型,默认为int类型,并返回一个随机数,一般为0XCCCCCCCC 在C++中,如果函数没有返回值则必须指定为void型,否则编译不能通过 4、参数列表 在C语言中,函数没有指定的参数列表时,默认可接收任意多个参数 在C++中,有严格的参数类型检测,没有参数列表的函数,默认为void,不接收任何参数。 缺省参数 缺省参数是声明和定义时函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用默认值,否则使用指定的实参。 如下代码: include<iostream>using namespace std;void test(int a = 1){cout << a << endl;}int main(){test();test(10);//输出的结果是1return 0;//函数输出结果是10}而缺省参数是分为两类的,一类是全缺省,一类是半缺省。 首先是全缺省,全缺省参数所有参数都有默认值,如果没有手动传参,那么编译器会使用默认参数列表中的参数。但是这里值得注意的是,如果传参的时候只传了部分参数,那么该值会被从左至右匹配。 代码示例: include<iostream>using namespace std;void test(int a = 1,int b = 2, int c = 3){cout << a << " " << b << " " << c << endl;}int main(){test();//1 2 3test(10);//10 2 3test(10, 20);//10 20 3test(10, 20, 30);//10 20 30return 0;} ...

April 26, 2019 · 2 min · jiezi

PHP源码学习20190319-PHP引用

【PHP源码学习】2019-03-19 PHP引用baiyan 全部视频:https://segmentfault.com/a/11... 原视频地址:http://replay.xesv5.com/ll/26... 由于这个系列的视频后面会再次细讲垃圾回收,那么我们今天先复习一下PHP中的引用,为后面做一个铺垫,后续的笔记会详细讲解垃圾回收器的相关运行原理。 PHP7中的引用引用:可以通过不同的变量名,访问同一个变量内容。PHP7中的引用通过让两个变量指向同一块内存空间实现了上述特性。在进行引用赋值后,等号左右两边的变量均变成了引用类型(IS_REFERENCE)。这块公用的内存空间就是PHP7为引用类型的变量专门创建的一个结构体,叫做zend_reference。代码示例:$a = 1;echo $a;$b = &$a; //$b是$a的引用echo $a;echo $b;unset($b);echo $a;我们用gdb调试以上代码:首先执行$a = 1;并且打印$a的值,$a就是一个普通的zval,其类型是IS_LONG,很好理解: 执行关键的一步:$b = &$a,打印$a的值,观察$a的存储情况: 观察上图,可以发现$a的type变成了10 (IS_REFERENCE)类型,并且ref字段指向了一个新的结构体,这就是zend_reference,zend_reference中存储着$a与$b共同的值1,由于$a与$b同时引用着这个结构体,故此时该结构体的refcount = 2。接下来打印$b,观察$b的存储情况: 观察上图,发现与$b的type也是IS_REFERENCE类型,且ref字段也指向了一个zend_reference结构体,比较$a与$b指向的zend_reference,二者地址相同,说明指向了同一个zend_reference结构体。此时两个变量的存储情况如下图所示: 接下来执行unset($b),观察$a以及zend_reference的存储情况,我们看是否符合预期: 我们看到unset($b)之后,$a所指向的zend_reference的refcount由2变为1,说明现在只有$a引用着这个结构体,b不再引用这个结构体,其类型变成了IS_UNDEF类型,代码执行完毕。那么我们看一下zend_reference结构体的基本结构:struct _zend_reference { zend_refcounted_h gc; //gc相关,存有refcount zval val; //引用类型的变量值存在这个zval中的zend_value字段中。简单类型的值直接存在这里,复杂类型的值存储对应数据结构的指针,来找到这个变量的值,和之前讲基本变量时候讲过的一样。};这个结构体一共只有2个字段,gc字段中是zend_refcounted_gc结构体类型,其中存储了引用计数;val字段存储了引用类型变量的值(简单类型如整型、浮点型的值直接存在这里,复杂类型存对应数据结构的指针,与之前讲基本变量的时候讲过的一样)。这样相当于加了一个中间层,使得原始的zend_string或zend_array在内存中只有1份,方便管理与维护。循环引用问题我们首先构造一个循环引用:<?php$a = ['time' => time()];echo $a;$a[] = &$a; //循环引用echo $a;unset($a);echo $a;注意:由于开启opcache的PHP7会在数组初始化的元素全部为常量元素的时候,将其优化成不可变数组(immutable array),这里的引用计数值refcount = 2只是一个伪引用计数,所以我们使用$a = ['time' => time()],让其初始化后的refcount为正常的1。]见下图: 利用gdb调试这段代码:执行完$a初始化并打印$a,refcount为1,type为7(IS_ARRAY)而此时的ref字段中的值是非法地址,说明此时还没有生成中间的zend_reference结构体: 继续执行下一行$a[] = &$a; 观察下图中绿色方框的含义: - $a的zval中的ref指向zend_reference结构体- zend_reference结构体中的zval字段中的arr指针指向了原始的zend_array- zend_array中的arData指针指向了bucket类型- zend_array中的bucket数组元素也是一个IS_REFERENCE类型,它又指回到同一个zend_reference结构体: 根据gdb调试情况画出内存结构图: 由于有两个东西指向zend_reference结构体(一个是$a,一个是$a数组中的一个元素),所以refcount = 2。原始的zend_array中也有一个refcount字段,由于只有一个zend_reference指向这个zend_array,所以refcount = 1。接下来继续执行unset($a): ...

April 25, 2019 · 1 min · jiezi

C-语言和-CC-的区别在什么地方

其实吧,个人感觉就是:任务:把大象放到冰箱里。C:C语言是一个极其高冷的人,因此回答都是冷冷的:我:你好C语言,我想把大象放到冰箱里,帮我做好不好?C:好我:那我们要怎么做呢?C:猜我:额。。。是不是应该先创造一只大象?C:是我:怎么创造呢?C:猜我只好去翻了一下文档,哦,malloc一块内存啊。我:好的,我用malloc,申请一块空间放大象好不好?C:好我:额。。。大象怎么构造呢?C:猜我。。。去看了看大象的结构我:好吧。。。我定义了一个大象的数据结构,接下来怎么做?C:猜我心里一阵说不出的感觉。。。我:哦好吧,我创造一个冰箱,步骤应该和大象差不多。C:嗯我翻看了冰箱的结构,然后定义了一个冰箱的struct。我:好了,冰箱构造出来了,怎么放呢?C:哼我。。。默默在Stack Overflow上输入"如何把大象放进冰箱 C"。我:终于找到答案了,定义一个方法,把大象的鼻子放进去、眼睛放进去、耳朵放进去。OK,都放进去了。C,你输出一下看看吧。C:烫烫烫烫烫烫烫我:哈哈哈哈C你终于不高冷了。。。我:哎,你咋不说话了?C?你发烧了吗?立刻一盆水倒上去。结束。 如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。C++:C++是一个知识渊博的孩子,相对年轻,也没那么高冷。我:C艹,我们把大象放冰箱好吗?C++:滚说话的不高冷呢?我:额我错了,亲爱的C++,我们把大象放冰箱好吧。C++:好的么么哒,大象的数据类型已经有人定义好了,冰箱也有人定义好了,你需要分别构造一个哦。我:好的。于是我翻看了文档后知道了用new来构造我:OK,亲爱的C++,我想构造一个大象。C++:好的,构造大象的方法有13个,请问你选择哪一种呢?我。。。我:你介绍一下吧。C++:OK,首先可以在栈上面构造,这么做的好处呢是可以方便资源管理,而且语法也好看一些,可以利用RAII,如果你不知道什么事RAII,你可以去cppreference上面查一下,当然你也可以在堆上构造,可以构造一只很大很大的大象,还有。。。。。。。一个月过后,C++终于讲完了。我也长满了胡须。。。刮了胡子之后继续工作我:好的,C++,我就在栈上面构造一只大象吧。C++:你不爱我了。我:???C++:你都没有说“亲爱的”。我。。。我:好吧,亲爱的C++,我想简单构造一只大象。C++:好的呢,你可以选择构造一只非洲象还是美洲象还是南极洲象,象的肤色是什么样子的你可以设定一下,象的屁股大小你可以设定一下,象的性别和性取向你看要什么。。。。。。我:我就想要一只简单的,默认的就好。C++:好的,构造出来了一只,你可以选择怎么初始化它,有13种初始化方法,推荐使用最新的统一初始化。我。。。我:统一初始化C++:好的,我们可以构造冰箱了。我:好。。。经过一个月的选择,终于构造出了冰箱。C++:好的,冰箱提供了addElephant方法,可以直接使用哦。我:哇太棒了,好的,我就用这个方法。C++:这个方法提供了多种重载,可以拷贝,移动,也可以直接转发,省的在移入冰箱之前构造一个大象。我:。。。你为啥不早说C++:你为啥不早问。我:就用移动的吧。C++:OK,请用std::move将构造好的大象转为右值引用。我一脸懵,但是还是照办。我:好了C++:OK,开始放入冰箱了哦,速度极快的呢。突然C++:报错了报错了,分子和原子不是可以转换的类型,大熊猫和爱迪生之间没有+运算符等1556项错误。我。。。。。。。。。我:网上找资料,说看最后一行就差不多了,好的看看。嗯,看起来是第31行出错了,错误是什么呢?报错信息啥也没说明白啊。随便改改吧。编译,运行。C++:好的,已经将臀围12米,左臂长13米,右臂长14米,喜欢公大象,没有结婚生子,从小家教不错,熟读四书五经的非洲母大象放入橘黄色,五十米高,六十米宽,三百米厚,温度有零下三百度,制冷剂的牌子是湖澈牌,额定电压220V的冰箱里。我。。。长舒了一口气。C#:我:我想把大象放进冰箱。C#:好的主人,我爸爸微软已经写好了大象类,也写好了冰箱类,你只需new一下就好了。我:OK,new好了,放入冰箱。C#:好的,已经放入,使用了冰箱的拓展方法addElephant<>()方法。我:嗯,你表现得很好,能不能放的稍微快点儿,刚刚C++放的就很快。C#:为了您的安全,不能。我:额。。。那我想调节一下大象耳朵的尺寸。C#:对不起,不能调节。您可以设定大象的耳朵形状,已为您定义好多种耳朵形状,您还可以调整大象的肤色,已为您定义好多种肤色。我:算了不调了,就这样吧。C#:好的。如果您需要速度,或者需要对大象的每个细节进行把握,还可以去看看我的弟弟,C++/CLR,他可以完成您提出的这些功能。您也可以将他带到我这里来,我们可以一起工作的。你也可以把C++完成的工作导出到二进制形式,我可以直接使用的。我:好的谢谢,不用了。C#:好的,祝您生活愉快。Python:我:我想把大象放到冰箱里。Python:好的,import这个“把大象放入冰箱”模块,然后使用run方法即可。我:OK谢啦。Python:不用。

April 25, 2019 · 1 min · jiezi

新手晕针看过来C指针详解经典非常详细

前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 下面让我们先从简单的类型开始慢慢分析吧: int p; //这是一个普通的整型变量int p; //首先从P 处开始,先与结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型.所以P是一个返回整型数据的指针int p[3]; //首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的,所以P 是一个由整型数据组成的数组int p[3]; //首先从P 处开始,先与[]结合,因为其优先级比高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的,所以P 是一个由返回整型数据的指针所组成的数组int (p)[3]; //首先从P 处开始,先与结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的.所以P 是一个指向由整型数据组成的数组的指针int *p; //首先从P 开始,先与结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据.由于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针int (p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了. 一、细说指针 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。让我们分别说明。 先声明几个指针放着做例子: 例一: (1)int*ptr;(2)char*ptr;(3)int**ptr;(4)int(*ptr)[3];(5)int(ptr)[4];1.指针的类型 从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型: (1)intptr;//指针的类型是int (2)charptr;//指针的类型是char (3)intptr;//指针的类型是int (4)int(ptr)[3];//指针的类型是int()[3] (5)int(ptr)[4];//指针的类型是int()[4] ...

April 24, 2019 · 3 min · jiezi

PHP源码学习20190318-复习前面的内容

【PHP源码学习】2019-03-18 复习前面的内容baiyan 全部视频:https://segmentfault.com/a/11... 原视频地址:http://replay.xesv5.com/ll/24... 本笔记中部分图片截自视频中的片段,图片版权归视频原作者所有。 malloc函数深入在PHP内存管理1笔记中提到,malloc()函数会在分配的内存空间前面额外分配32位,用来存储分配的大小和几个标志位,如图: 那么究竟是否是这样的呢?我们写一段测试代码验证一下:#include <stdlib.h>int main() { void *ptr = malloc(8); return 1;}利用gdb调试这段代码: 首先打印ptr的地址,为0x602010,利用x命令往后看20个内存单元(1个内存单元 = 4个字节),故一共展示了80个字节,后面的x是以16进制打印内容。我们发现紧邻0x602010地址的上面32位均是0,没有任何内容,不符合我们的预期。上图只是一个最简单的思路,但绝大多数操作系统是按照如下的方式实现的:操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表(Free List)。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块(根据不同的算法而定(将最先找到的不小于申请的大小内存块分配给请求者,将最合适申请大小的空闲内存分配给请求者,或者是分配最大的空闲块内存块)。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。结构体与联合体结构体在PHP内存管理2笔记中,我们谈到了一种特殊情况: 在b是char类型的时候,a和b的内存地址是紧邻的;如果b是int类型的话,就会出现如图所示的情况。我们可以这样记忆:不看b之后的字段,a和b之前也是按照它们的最小公倍数对齐的(如果b是int类型,a和b的最小公倍数是4,按4对齐;如果b是char类型,最小公倍数为1,按1对齐,就会出现a和b紧邻的情况)如果不想对齐,有如下解决方案: 编译的时候不加优化参数代码层面:在struct后加关键字,例如redis中的sds简单动态字符串的实现: struct __attribute__ ((packed)) sdshdr16 { uint16_t len; uint16_t alloc; unsigned char flags; char buf[]; }联合体所有字段共用一段内存,用于PHP中变量值的存储(因为变量只有一种类型),也可以用来判断机器的大小端问题。宏定义宏就是替换。关于下面这段代码的复杂宏替换问题,在PHP内存管理3笔记中已经有详细解释,此处不再赘述。#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,static const uint32_t bin_data_size[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)};关于C语言宏定义中的##等特殊符号的用法,参考:#define宏定义中的#,##,@#, 这些符号的神奇用法PHP7中的基本变量在PHP7中,所有变量都以zval结构体来表示。一个zval是16字节;在PHP5中,一个zval是48字节。struct _zval_struct { zend_value value; union u1; union u2;};存储变量需要考虑两个要素:值与类型。变量值的存放在PHP7中,变量的值存在zend_value 这个联合体中。只有整型和浮点型是直接存在zend_value中,其余类型都只存放了一个指向专门存放该类型的结构体指针。这个联合体共占用8字节。typedef union _zend_value { zend_long lval; //整型 double dval; //浮点 zend_refcounted *counted; //引用计数 zend_string *str; //字符串 zend_array *arr; //数组 zend_object *obj; //对象 zend_resource *res; //资源 zend_reference *ref; //引用 zend_ast_ref *ast; //抽象语法树 zval *zv; //内部使用 void *ptr; //不确定类型,取出来之后强转 zend_class_entry *ce; //类 zend_function *func;//函数 struct { uint32_t w1; uint32_t w2; } ww; //这个union一共8B,这个结构体每个字段都是4B,因为所有联合体字段共用一块内存,故相当于取了一半的union} zend_value;变量类型的存放在PHP7中,其变量的类型存放在zval中的u1联合体中:... union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* 在这里用unsigned char存放PHP变量值的类型 */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1;...PHP7中所有的变量类型:/* regular data types */#define IS_UNDEF 0#define IS_NULL 1#define IS_FALSE 2#define IS_TRUE 3#define IS_LONG 4#define IS_DOUBLE 5#define IS_STRING 6#define IS_ARRAY 7#define IS_OBJECT 8#define IS_RESOURCE 9#define IS_REFERENCE 10/* constant expressions */#define IS_CONSTANT 11#define IS_CONSTANT_AST 12/* fake types */#define _IS_BOOL 13#define IS_CALLABLE 14#define IS_ITERABLE 19#define IS_VOID 18/* internal types */#define IS_INDIRECT 15#define IS_PTR 17#define _IS_ERROR 20PHP7中的字符串字符串基本结构设计字符串存储的数据结构两大要素:字符串值和长度。PHP7字符串存储结构的设计:struct _zend_string { zend_refcounted_h gc; /*引用计数,与垃圾回收相关,暂不展开*/ zend_ulong h; /* 冗余的hash值,计算数组key的哈希值时避免重复计算*/ size_t len; /* 长度 */ char val[1]; /* 柔性数组,真正存放字符串值 */};由为什么存长度引申出二进制安全的问题。二进制安全:写入的数据和读出来的数据完全相同,就是二进制安全的,详情见PHP字符串笔记字符串写时复制看下面一段PHP代码:<?php$a = "string" . time("Y-m-d");echo $a;$b = $a;echo $a;echo $b;$b = "new string";echo $a;echo $b;利用gdb调试这段代码,观察其引用计数情况。在第一个echo语句处打断点,并查看$a中zend_stinrg中的引用计数gc.refcount = 1(下简称refcount)。因为现在只有一个$a引用zend_string。 ...

April 23, 2019 · 2 min · jiezi

【PHP源码学习】2019-03-13 PHP字符串笔记

【PHP源码学习】2019-03-13 PHP字符串笔记baiyan 全部视频:https://segmentfault.com/a/11... 源视频地址:http://replay.xesv5.com/ll/24... 字符串的设计过程在C99的柔性数组标准未发布之前,我们如果想设计一个数据结构,存储一个字符串,可以很容易地想出如下代码: struct string{ ... int len; //存长度(至于为什么存长度下文会讲到) char* val; //存真正的字符串值 };那么我们发现,这样做有如下缺点: 访问字符串值的时候,需要先访问结构体,在访问指针所指向的内存空间,需要2次内存访问,效率低下。释放字符串内存空间的时候,需要先释放char *val指针所指向的内存空间,再释放结构体本身的内存空间,效率同样低下,而且这两个操作顺序不能颠倒。那么如何改进呢?很容易想到,我们将字符串值和结构体存储在一片连续的内存空间就可以了。这样的话,访问字符串与释放字符串的内存空间,均仅需1次内存访问,在C99柔性数组标准发布之前,改进代码的方式如下:int main() { struct string{ int len; }; typedef struct string str; char *s = "he"; str *p = (str*)(malloc(sizeof(str) + strlen(s) + 1)); //分配足够存下一个字符串的结构体 p->len = strlen(s); memcpy(p + 1, s, strlen(s)); //将字符串拷贝到紧邻结构体的内存处}小插曲:这个代码的第一版,我还犯了一个指针加法的错误,见:关于memcpy一个字符串到紧邻结构体内存空间处的疑问我们利用gdb调试一下这段代码: 首先我们应该给这个结构体分配4 + 2 + 1 = 7字节的内存空间,但是由于内存对齐的原因,最终分配了8字节大小的空间。结构体本身和len字段的地址均是0x602010,len字段的长度为4B,指针加上4B的len字段长度之后,就应该是字符串he的起始地址,即0x602014,将其强转为char *,发现正好就是我们存的字符串值"he"。注意不是p+4,而是p+1。因为p+4 = p+4*sizeof(指针p的类型)由于这样编写代码过于繁琐,所以C99干脆制定一个标准,使用柔性数组代替上述写法。其实使用的计算方法和上面一段代码是一样的,只不过换了一种简化的写法而已,这段代码最终内存中的存储情况如下: PHP7中字符串的实现借助上文讲到的字符串数据结构设计思想,PHP中是这样设计字符串的,它的结构体叫做zend_string:struct _zend_string { zend_refcounted_h gc; /*引用计数,与垃圾回收相关,暂不展开*/ zend_ulong h; /* 冗余的hash值,计算数组key的哈希值时避免重复计算*/ size_t len; /* 长度 */ char val[1]; /* 柔性数组,真正存放字符串值 */};第一个问题:为什么要存长度len?不存长度,直接和C语言一样通过字符串的'\0'来判断字符串结束不行吗?不行。这里有一个二进制安全的问题。 ...

April 22, 2019 · 1 min · jiezi

2019年Python、Java、C++学哪个更好?薪资更高?

首先,我认为编程语言是没有最好,只有最合适。但是未来预测这种事还是留给大神,这边就分享给楼主一些“干货”最后,如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 Python 难度:★ 欢迎度:★★★★☆ 创始于:1991年 学完之后可以干什么: web开发、应用开发、大数据、数据挖掘、科学计算、机器学习、人工智能、运维、自然语言处理等等等。 就业薪资: 2019年Python、Java、C++学哪个更好?薪资更高?Python由于应用广泛,又是人工智能的主要开发语言,市场上相关人才较少,所以平均薪资能够达到2万以上。 Python的优点: • 易于学习:就像是楼主说的,逛了下论坛发现做了几年Java或者是C++的,几天就可以写Python了。但是这个不是什么坏事,入门来说,从简入难,或者从难入简,都是很好的选择。 • 库:库都是免费的,并且有很多库和函数把编程变得相对容易很多。 • 物联网:Python也许是会成为物联网当中最受欢迎的语言,我们都知道树莓派这样的新平台都是基于Python开发的。 Python的缺点: • 速度:开发速度是快,比如java100行代码python20行就搞定了。但是作为解释型的语言来说,比编译型语言的速度慢很多。 • 移动端:Python在移动计算方面是弱的,很少有智能机的应用是Python开发的 • 设计:python是动态型的语言,需要更多的测试以及错误仅仅是在运行的时候展示的。 2.Java 难度:★ ★ ★ 欢迎度:★ ★ ★ ★ ★ 创始于:1995年 Java可以做什么: 安卓和IOS的应用开发、视频游戏开发、桌面GUI、软件开发等等; 就业薪资: 2019年Python、Java、C++学哪个更好?薪资更高?Java是老牌语言,但是由于市场上相关开发人才较多,竞争激烈,薪资趋于平稳。 Java的优点: • Java开发人员需求量大:这个是根据统计得出的。JAVA在很多语言当中,是需求量最大的; • 进化语言:首先C++是基于C语言优化的,Java是被优化过来的。而且在这人平台是增加了很多的功能,lambda等功能 • 安卓应用开发:谷歌的安卓移动平台是世界第一的移动平台,编写安卓应用开发者使用的主要语言是Java; Java的缺点: • 使用大量的内存:Java和C++相比使用更多的内存所以占用的内存就更大 • 学习曲线:这边指的是Java虽然不是最简单的入门语言,但是也不是最难- -|| • 启动时间慢:用java写过安卓的应用的人应该都知道。同样的代码在模拟器中启动是非常缓慢的事情。 3.C++ 难度:★★★★☆ 受欢迎度:★★★★☆ 创始于:1983年 C++目前是被看做编写大型程序应用最好的面向对象编程语言。C++是C语言的升级版本; C++用来做什么: 搜索引擎、软件开发、操作系统、视频游戏等 就业薪资: 2019年Python、Java、C++学哪个更好?薪资更高?C++开发工程师的起薪一般在一万元左右,高级工程师的薪资能够达到2万以上。 C++的优点: 在熟练掌握了C++之后,处在各个位置当中都能成为佼佼者。使用C/C++的话,帮助我们更理解其他编程语言。比如说是Java或者是Python是如何进行内存管理以及如何处理缺陷; 能调整性能:C++能允许调整你的应用性能以及影响所有计算机性能,它写起来的话比Java来说就并不是很友善,但是我们也可以利用C++做很多事情; C++的缺点: 学习困难:C++相对来说学习难度很大,但是如果我们可以流畅使用的话,会圆圆超过其他的程序员; 尺寸很大:C++可以称为巨大,它的大部分功能互相影响是机器复杂的方式。没有开发者是可以使用全部提供的组成部件。但是会发挥大量的时间来挑选你的程序的子部件的。 ...

April 22, 2019 · 1 min · jiezi

给大学生就业支招--精通C/C++能找到非常好的工作!

电类专业大学生都学过C或C++,但没有引起大家的重视。电类专业毕业生要想有一份高薪水的工作,从事与 嵌入式 系统应用技术有关的开发工作是首选。软硬通吃当然是高手,但事实上只要你精通C/C++,哪怕其它课程完全按部就班地学一遍,等到毕业时要找一份好工作也是非常容易的。 如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。 精通 C/C++的同学的就业方向主要有基于X86计算机的驱动程序开发工程师、WinCE、 Linux 与软件开发工程师等职位。 到了大四时,很多同学都希望毕业后能够从事WinCE、Linux方向的嵌入式软件开发工作,不要说对C++不熟练,而且对C程序设计也未必达到入门级的水平,这样的基础从何下手呢?所以在校大学生不论你目前处于那个年纪,如果你对 C语言 还不精通,请立即行动起来投入到程序的设计当中去。 对于刚跨入大一的学生来说,如果你所在的大学还未开发C语言,请马上购书、上机实践自学程序设计,否则等到你明白的时候就已经太晚了。就算你在大学四年阶段“瞎混”,但只要你每天使用C/C++写程序,四年之后你一定会成长为高手,难道你还愁没有好工作吗? 下面将给大家介绍一本《C++程序设计教程》(第二版,作者钱能,清华大学出版社)好书,分别为基础部分(概述、基本编程语句、数据类型和计算表达)、过程化编程(函数机制、性能和程序结构)、面向对象编程技术(类、对象生灭、继承和基于对象编程)和高级编程(多态、抽象类、模版和异常)。可以选择其中的第一、二部分作为C程序设计教学的内容,第三、四部分作为C++程序设计的教学内容,第一、二部分和第三、四部分分别可以作为一个学期约64个理论实践课时的教学,其中上机实践可占到24学时,如果有条件的话,可以增加更多的上机实践时间。 建议有志成才的同学自发地组织C/C++程序设计课外兴趣小组,然后在此基础上再发展WinCE、Linux兴趣小组,按照笔者前面已经发表的文章中的办法开展学生社团活动,强化动手能力和进行创新实践。

April 21, 2019 · 1 min · jiezi

【PHP源码学习】2019-03-12 PHP基本变量笔记

【PHP源码学习】2019-03-12 PHP基本变量笔记baiyan 全部视频:https://segmentfault.com/a/11... 源视频地址:http://replay.xesv5.com/ll/24... 引入及基本概念变量本质上就是给一段内存空间起了个名字如果让我们自己基于C语言设计一个存储如$a = 1变量的数据结构,应该如何设计?变量的基本要素是类型与值,其中部分类型还有其他的描述字段(如长度等)首先应该定义一个结构体作为基本的数据结构第一个问题:变量类型如何存储? 答:用一个unsigned char类型的字段存足够,因为unsigned char类型最多能够表示2^8 = 256种类型。PHP7中以zval表示所有的变量,它是一个结构体。先看zval的基本结构:typedef unsigned char zend_uchar;struct _zval_struct { zend_value value; /* 下面会讲 */ union { struct { ZEND_ENDIAN_LOHI_4( //大小端问题,详情看"PHP内存管理3笔记” zend_uchar type, //注意这里就是存放变量类型的地方,char类型 zend_uchar type_flags, //类型标记 zend_uchar const_flags, //是否是常量 zend_uchar reserved) //保留字段 } v; uint32_t type_info; } u1; union { uint32_t next; /* 数组模拟链表,在下文链地址法解决哈希冲突时使用 */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ uint32_t extra; /* not further specified */ } u2;};注意关注中文注释的部分,PHP就是利用C语言的unsigned char类型,存储了所有变量的类型。在PHP中,所有变量的类型如下:/* regular data types */#define IS_UNDEF 0#define IS_NULL 1#define IS_FALSE 2#define IS_TRUE 3#define IS_LONG 4#define IS_DOUBLE 5#define IS_STRING 6#define IS_ARRAY 7#define IS_OBJECT 8#define IS_RESOURCE 9#define IS_REFERENCE 10/* constant expressions */#define IS_CONSTANT 11#define IS_CONSTANT_AST 12/* fake types */#define _IS_BOOL 13#define IS_CALLABLE 14#define IS_ITERABLE 19#define IS_VOID 18/* internal types */#define IS_INDIRECT 15#define IS_PTR 17#define _IS_ERROR 20第二个问题:变量的值如何存储? 答:如果是a是1用int;如果是1.1,用double;是'1'用char *等等,但是变量的值的类型只有1种,不可能同时用到多种类型去存值,故我们可以把这一大堆东西放到1个union里面即可,源码中存储变量类型的联合体叫做zend_value:typedef union _zend_value { zend_long lval; //存整型值 double dval; //存浮点值 zend_refcounted *counted; //存引用计数值 zend_string *str; //存字符串值 zend_array *arr; //存数组值 zend_object *obj; //存对象值 zend_resource *res; //存资源值 zend_reference *ref; //存引用值 zend_ast_ref *ast; //存抽象语法树 zval *zv; //内部使用 void *ptr; //不确定类型,取出来之后强转 zend_class_entry *ce; //存类 zend_function *func;//存函数 struct { uint32_t w1; uint32_t w2; } ww; //这个union一共8B,这个结构体每个字段都是4B,因为所有联合体字段共用一块内存,故相当于取了一半的union} zend_value;由于某些类型的变量需要额外的一些描述信息(如字符串、数组),其复杂度更高,为了节省空间,就只在zend_value结构体中存了一个结构体指针,其真正的值在zend_string、zend_array这些结构体中(下面会讲)。zend_value类型是一个联合体,共占用8B。因为变量只有一种类型,所以就可以利用联合体共用一块内存的特性,来存储变量的类型。注意最后一个结构体是一个小技巧,通过取ww结构体的其中一个字段,可以取到联合体变量高4位或者低4位,这样就不用手动编写多余代码去取了。在PHP7中,zend_value占用8B,而u1占用4B,u2占用4B,经过内存对齐,一个zval占用16B,相较PHP5,占用的内存大幅减少。利用gdb查看变量底层存储情况示例代码:<?php$a = 1;echo $a;$b = 1.1;echo $b;$c = "hello";echo $c;$d = [1,2,3];echo $d;首先在ZEND_ECHO_SPEC_CV_HANDLER打一个断点。在PHP虚拟机中,一条指令对应一个handler,这里对应的就是echo的语法。首先我们执行到了$a = 1处,打印这个z变量的值,可以看到lval = 1,它就是用来存放$a的值的。然后再关注联合体u1中的type字段的值为4,对照上文类型对照表,正好对应IS_LONG类型。记录下它的地址0x7ffff381c080,下文将要使用。 ...

April 21, 2019 · 2 min · jiezi

用C / C ++实现BP神经网络

缘起最近跟着老师在学习神经网络,为了更加深刻地理解这个黑盒,我打算自己用C/C++将其实现一遍。今天忙活了好一会儿,终于实现了一个BP神经网络,后期还会陆续实现CNN神经网络之类的,也会发上来和大家一起分享的因为最近比较忙,所以这里直接放代码了,关于一些原理以及自己的一点见解会在有空的时候整理出来的代码main.cpp#include <iostream>#include <vector>#include “BPUtils.h"using namespace std;/* run this program using the console pauser or add your own getch, system(“pause”) or input loop /vector<vector<double>>dataTest;vector<double>dataTestY;vector<vector<double>>trainDataX;vector<double>trainDataY;int main() {// double m1[3][1]={{1},{2},{3}};// double m2[1][4]={1,2,3,4};// double m3[3][4];// dott(&m1[0][0],&m2[0][0],&m3[0][0],3,1,4);// for(int i=0;i<3;i++){// for(int j=0;j<4;j++){// cout<<m3[i][j]<<” “;// }// cout<<endl;// } createTrainSet(); createTestSet(); guiYiHua(dataTest); guiYiHua(trainDataX); NeuralNetwork nn(2,44,2); nn.train(trainDataX,trainDataY);// for(int i=0;i<trainDataX.size();i++){// for(int j=0;j<trainDataX[i].size();j++){// cout<<trainDataX[i][j]<<” “;// }// cout<<endl;// }// for(int i=0;i<trainDataX.size();i++){// cout<<trainDataY[i]<<” “;// }//// cout<<endl<<”———————————————————"<<endl;//// for(int i=0;i<dataTest.size();i++){// for(int j=0;j<dataTest[i].size();j++){// cout<<dataTest[i][j]<<" “;// }// cout<<endl;// }// for(int i=0;i<dataTestY.size();i++){// cout<<dataTestY[i]<<” “;// }// NeuralNetwork nn(2,4,3);// vector<vector<double>>dataX;// vector<double>dataY;// for(int i=0;i<4;i++){// vector<double>vec;// for(int j=0;j<2;j++){// vec.push_back(i+j);// }// dataX.push_back(vec);// }// for(int i=0;i<4;i++){// for(int j=0;j<2;j++){// cout<<dataX[i][j]<<” “;// }// cout<<endl;// }// for(int i=0;i<4;i++){// dataY.push_back(i);// }// nn.train(dataX,dataY); return 0;}BPUtils.h#ifndef BP_UTILS#define BP_UTILS#include <cmath>#include <cstdlib>#include <iostream>#include <vector>#include <ctime>#include <string.h>#include <cstdio>#include <fstream>#define random(x) (rand()%x)using namespace std;#define MAXSIZE 99//全局变量//测试集extern vector<vector<double>>dataTest;extern vector<double>dataTestY;extern vector<vector<double>>trainDataX;extern vector<double>trainDataY;vector<string> split(const string& str, const string& delim) { vector<string> res; if(”" == str) return res; //先将要切割的字符串从string类型转换为char类型 char * strs = new char[str.length() + 1] ; //不要忘了 strcpy(strs, str.c_str()); char * d = new char[delim.length() + 1]; strcpy(d, delim.c_str()); char p = strtok(strs, d); while(p) { string s = p; //分割得到的字符串转换为string类型 res.push_back(s); //存入结果数组 p = strtok(NULL, d); } return res;}double getMax(vector<vector<double>>dataSet){ double MYMAX=-999; for(int i=0;i<dataSet.size();i++){ for(int j=0;j<dataSet[i].size();j++){ if(MYMAX<dataSet[i][j]){ MYMAX=dataSet[i][j]; } } } return MYMAX;}double getMin(vector<vector<double>>dataSet){ double MYMIN=999; for(int i=0;i<dataSet.size();i++){ for(int j=0;j<dataSet[i].size();j++){ if(MYMIN>dataSet[i][j]){ MYMIN=dataSet[i][j]; } } } return MYMIN;}//数据归一化//一般是x=(x-x.min)/x.max-x.minvoid guiYiHua(vector<vector<double>>&dataSet){ double MYMAX=getMax(dataSet); double MYMIN=getMin(dataSet); for(int i=0;i<dataSet.size();i++){ for(int j=0;j<dataSet[i].size();j++){ dataSet[i][j]=(dataSet[i][j]-MYMIN)/(MYMAX-MYMIN); } }}//创建测试集的数据void createTrainSet(){ fstream f(“train.txt”); //保存读入的每一行 string line; vector<string>res; int ii=0; while(getline(f,line)){ res=split(line,"\t"); vector<double>vec1; for(int i=0;i<res.size();i++){ //cout<<res[i]<<endl; char ch[MAXSIZE]; strcpy(ch,res[i].c_str()); if(i!=2){ vec1.push_back(atof(ch)); }else{ trainDataY.push_back(atof(ch)); } } trainDataX.push_back(vec1); ii++; }}//创建训练集的数据void createTestSet(){ fstream f(“test.txt”); //保存读入的每一行 string line; vector<string>res; int ii=0; while(getline(f,line)){ res=split(line,"\t"); vector<double>vec1; for(int i=0;i<res.size();i++){ //cout<<res[i]<<endl; char ch[MAXSIZE]; strcpy(ch,res[i].c_str()); if(i!=2){ vec1.push_back(atof(ch)); }else{ dataTestY.push_back(atof(ch)); } } dataTest.push_back(vec1); ii++; }}//sigmoid激活函数double sigmoid(double x){ return 1/(1+exp(-x));}//sigmoid函数的导数double dsigmoid(double x){ return x(1-x);}class NeuralNetwork{public: //输入层单元个数 int inputLayers; //隐藏层单元个数 int hidenLayers; //输出层单元个数 int outputLayers; //输入层到隐藏层的权值 //行数为输入层单元个数+1(因为有偏置) //列数为隐藏层单元个数 vector<vector<double>>VArr; //隐藏层到输出层的权值 //行数为隐藏层单元个数 //列数为输出层单元个数 vector<vector<double>>WArr;private: //矩阵乘积 void dot(const double* m1,const double* m2,double m3,int m,int n,int p){ for(int i=0;i<m;++i) //点乘运算 { for(int j=0;j<p;++j) { ((m3+ip+j))=0; for(int k=0;k<n;++k) { ((m3+ip+j))+=((m1+in+k))((m2+kp+j)); } } } } void vectorToArr1(vector<vector<double>>vec,double arr,int n){ for(int i=0;i<vec.size();i++){ for(int j=0;j<vec[i].size();j++){ //cout<<endl<<vec[i][j]<<""<<i<<""<<j<<""; ((arr+in+j))=vec[i][j]; } //cout<<endl; } } void vectorToArr2(vector<double>vec,double arr){ for(int i=0;i<vec.size();i++){ ((arr+i))=vec[i]; } } void arrToVector1(double arr,vector<double>&vec,int m){ for(int i=0;i<m;i++){ vec.push_back(((arr+i))); } } //矩阵转置 void ZhuanZhi(const doublem1,double m2,int n1,int n2){ for(int i=0;i<n1;i++){ for(int j=0;j<n2;j++){ ((m2+jn1+i))=((m1+in2+j)); } } } //验证准确率时的预测 //输入测试集的一行数据 //ArrL2为输出层的输出 //eg.当我们要分成10类的时候,输出10个数,类似于该样本属于这10个类别的概率 //我们选取其中概率最大的类别作为最终分类得到的类别 void predict(vector<double>test,double ArrL2){// for(int i=0;i<test.size();i++){// cout<<“test[i]:"<<test[i]<<endl;// } //添加转置 test.push_back(1); double testArr[1][inputLayers+1]; //转成矩阵 vectorToArr2(test,&testArr[0][0]);// for(int i=0;i<inputLayers+1;i++){// cout<<“testArr:"<<testArr[0][i]<<endl;// } double dotL1[1][hidenLayers]; double VArr_temp[inputLayers+1][hidenLayers]; vectorToArr1(VArr,&VArr_temp[0][0],hidenLayers);// for(int i=0;i<inputLayers+1;i++){// for(int j=0;j<hidenLayers;j++){// cout<<VArr_temp[i][j]<<” “;// }// cout<<endl;// } //testArr[1][inputLayers+1] dot VArr[inputLayers+1][hidenLayers] dot(&testArr[0][0],&VArr_temp[0][0],&dotL1[0][0],1,inputLayers+1,hidenLayers);// for(int i=0;i<1;i++){// for(int j=0;j<hidenLayers;j++){// cout<<dotL1[i][j]<<” “;// }// cout<<endl;// } //隐藏层输出 double ArrL1[1][hidenLayers]; //double ArrL2[1][outputLayers]; for(int i=0;i<hidenLayers;i++){ ArrL1[0][i]=sigmoid(dotL1[0][i]); //cout<<ArrL1[0][i]<<endl; } double dotL2[1][outputLayers]; double WArr_temp[hidenLayers][outputLayers]; vectorToArr1(WArr,&WArr_temp[0][0],outputLayers); //ArrL1[1][hidenLayers] dot WArr[hidenLayers][outputLayers] dot(&ArrL1[0][0],&WArr_temp[0][0],&dotL2[0][0],1,hidenLayers,outputLayers); //输出层输出 for(int i=0;i<outputLayers;i++){ //ArrL2[0][i]=sigmoid(dotL2[0][1]); ((ArrL2+i))=sigmoid(dotL2[0][i]); //cout<<(ArrL2+i)<<endl; } } int getMaxIndex(vector<double>vec){ int index=-1; double MYMAX=-999; for(int i=0;i<vec.size();i++){ //cout<<vec.size()<<”"<<endl; //cout<<i<<”::::"<<vec[i]<<endl; if(MYMAX<vec[i]){ MYMAX=vec[i]; index=i; } } return index; }public: //构造函数,传入输入层,隐藏层,输出层单元个数 //并且构造权值矩阵 NeuralNetwork(int _inputLayers,int _hidenLayers,int _outputLayers){ this->inputLayers=_inputLayers; hidenLayers=_hidenLayers; outputLayers=_outputLayers; //构造V权值矩阵 for(int i=0;i<inputLayers+1;i++){ vector<double>vec; for(int j=0;j<hidenLayers;j++){ vec.push_back((double)rand()/RAND_MAX2-1); } VArr.push_back(vec); } for(int i=0;i<hidenLayers;i++){ vector<double>vec; for(int j=0;j<outputLayers;j++){ vec.push_back((double)rand()/RAND_MAX2-1); } WArr.push_back(vec); } } //开始训练 //传入训练集,预期的y值,学习效率,以及训练迭代的次数 //这里规定输入的数据为2列的数据 void train(vector<vector<double>>dataX,vector<double>dataY,double lr=0.03,int epochs=1000000){ double arrL1[1][hidenLayers]; //将VArr由vector转成arr double VArr_temp[inputLayers+1][hidenLayers]; double hangx_temp[1][inputLayers+1]; vectorToArr1(VArr,&VArr_temp[0][0],hidenLayers); double hangxT[inputLayers+1][1]; double hangxDotVArr[1][hidenLayers]; double arrL2[1][outputLayers]; double WArr_temp[hidenLayers][outputLayers]; double arrL2_delta[1][outputLayers]; double arrL1_delta[1][hidenLayers]; double E; double dao; double dotTemp[hidenLayers][outputLayers]; double WArr_tempT[outputLayers][hidenLayers]; double arrL1T[hidenLayers][1]; double dotTempp[inputLayers+1][hidenLayers]; srand((int)time(0)); //为数据集添加偏置 //eg.当我们输入的数据集为4X2的时候,需要为其在最后添加一列偏置,让其变成一个4X3的矩阵 for(int i=0;i<dataX.size();i++){ //最后一列为偏置 dataX[i].push_back(1); } //进行权值训练更新 for(int n=0;n<epochs;n++){ //随机选取一行样本进行更新 int iii=random(dataX.size()); //cout<<“iii:"<<iii<<endl; //得到随机选取的一行数据 vector<double>hangx=dataX[iii];// for(int i=0;i<hangx.size();i++){// cout<<hangx[i]<<”"<<endl;// } //隐藏层输出 //这里先计算输入矩阵与权值矩阵的点乘,再将其输入sigmoid函数中,得到最终的输出 //eg.输入4X2的dataX,我们先加上偏置变成4X3 //选取其中的一行数据1X3 //然后计算dataX与arrV(3XhidenLayers)的dot,得到一个1XhidenLayers的矩阵// for(int ii=0;ii<inputLayers+1;ii++){// for(int jj=0;jj<hidenLayers;jj++){// cout<<VArr[ii][jj]<<"—";// cout<<VArr_temp[ii][jj]<<" “;// }// cout<<endl;// } vectorToArr2(hangx,&hangx_temp[0][0]);// for(int i=0;i<inputLayers+1;i++){// cout<<hangx[i]<<”—"<<endl;// cout<<hangx_temp[0][i]<<""<<endl;// } //hangx[1][inputLayers+1] dot VArr[inputLayers+1][hidenLayers] dot(&hangx_temp[0][0],&VArr_temp[0][0],&arrL1[0][0],1,inputLayers+1,hidenLayers); //将点乘后的值输入到sigmoid函数中 for(int k1=0;k1<hidenLayers;k1++){ arrL1[0][k1]=sigmoid(arrL1[0][k1]); //cout<<arrL1[0][k1]<<endl; } vectorToArr1(WArr,&WArr_temp[0][0],outputLayers);// for(int ii=0;ii<hidenLayers;ii++){// for(int jj=0;jj<outputLayers;jj++){// cout<<WArr_temp[ii][jj]<<endl;// }// } //arrL1[1][hidenLayers] dot WArr_temp[hidenLayers][outputLayers] dot(&arrL1[0][0],&WArr_temp[0][0],&arrL2[0][0],1,hidenLayers,outputLayers); //cout<<outputLayers<<endl; //cout<<arrL2[0][1]<<endl;// for(int k1=0;k1<outputLayers;k1++){// arrL2[0][k1]=sigmoid(arrL2[0][k1]);//// // cout<<k1<<endl;//// cout<<arrL2[0][k1]<<endl;// } //求权值的delta //根据公式计算权值更新的delta for(int k1=0;k1<outputLayers;k1++){ arrL2[0][k1]=sigmoid(arrL2[0][k1]); // cout<<k1<<endl; //cout<<“arrL2[0][k1]:"<<arrL2[0][k1]<<endl; E=dataY[iii]-arrL2[0][k1]; //cout<<“E:"<<E<<endl; dao=dsigmoid(arrL2[0][k1]); //cout<<“dao:"<<dao<<endl; arrL2_delta[0][k1]=Edao; //cout<<“arrL2_delta[0][k1]:"<<arrL2_delta[0][k1]<<endl; }// for(int k1=0;k1<outputLayers;k1++){// //计算误差// E=dataY[iii]-arrL2[0][k1];// //对L2输出的结果求导// dao=dsigmoid(arrL2[0][k1]);//// cout<<“arrL2[0][k1]:"<<arrL2[0][k1]<<endl;//// cout<<“dataY[iii]:"<<dataY[iii]<<endl;//// cout<<“E:"<<E<<endl;//// cout<<“dao:"<<dao<<endl;// //计算delta// arrL2_delta[0][k1]=Edao;// }// for(int i=0;i<outputLayers;i++){// cout<<arrL2_delta[0][i]<<endl;// } //W矩阵转置 ZhuanZhi(&WArr_temp[0][0],&WArr_tempT[0][0],hidenLayers,outputLayers);// for(int i=0;i<outputLayers;i++){// for(int j=0;j<hidenLayers;j++){// cout<<WArr_temp[j][i]<<””;// cout<<WArr_tempT[i][j]<<” “;// }// cout<<endl;// } //arrL2_delta[1][outputLayers] dot WArr_tempT[outputLayers][hidenLayers] dot(&arrL2_delta[0][0],&WArr_tempT[0][0],&arrL1_delta[0][0],1,outputLayers,hidenLayers); //乘上L1输出的导数// for(int k1=0;k1<hidenLayers;k1++){// cout<<dsigmoid(arrL1[0][k1])<<endl;// } //乘上L1输出的导数 for(int k1=0;k1<hidenLayers;k1++){ double ii=arrL1_delta[0][k1]; arrL1_delta[0][k1]=ii*dsigmoid(arrL1[0][k1]); //cout<<ii<<”"<<dsigmoid(arrL1[0][k1])<<”"<<arrL1_delta[0][k1]<<endl; } //通过上面的delta更新权值WV ZhuanZhi(&arrL1[0][0],&arrL1T[0][0],1,hidenLayers);// for(int i=0;i<hidenLayers;i++){// cout<<arrL1T[i][0]<<endl;// } //arrL1T[hidenLayers][1] dot arrL2_delta[1][outputLayers] dot(&arrL1T[0][0],&arrL2_delta[0][0],&dotTemp[0][0],hidenLayers,1,outputLayers);// for(int k1=0;k1<outputLayers;k1++){// cout<<arrL2_delta[0][k1]<<endl;// }// for(int k1=0;k1<hidenLayers;k1++){// for(int k2=0;k2<outputLayers;k2++){// cout<<dotTemp[k1][k2]<<” “;// }// cout<<endl;// }// for(int k1=0;k1<outputLayers;k1++){// cout<<arrL2_delta[0][k1]<<endl;// } for(int k1=0;k1<hidenLayers;k1++){ for(int k2=0;k2<outputLayers;k2++){ //根据学习效率进行更新 //cout<<dotTemp[k1][k2]<<endl; WArr[k1][k2]+=(lrdotTemp[k1][k2]); //cout<<“WArr[k1][k2]:"<<WArr[k1][k2]<<endl; } } //转置 ZhuanZhi(&hangx_temp[0][0],&hangxT[0][0],1,inputLayers+1);// for(int i=0;i<inputLayers+1;i++){// cout<<hangxT[i][0]<<”))"<<endl;// } //hangxT[inputLayers+1][1] dot arrL1_delta[1][hidenLayers]// for(int k1=0;k1<hidenLayers;k1++){// //double ii=arrL1_delta[0][k1];// //arrL1_delta[0][k1]=iidsigmoid(arrL1[0][k1]);// cout<<arrL1_delta[0][k1]<<” “;// } //cout<<endl; dot(&hangxT[0][0],&arrL1_delta[0][0],&dotTempp[0][0],inputLayers+1,1,hidenLayers);// for(int i=0;i<inputLayers+1;i++){// for(int j=0;j<hidenLayers;j++){// cout<<dotTempp[i][j]<<” “;// }// cout<<endl;// } for(int k1=0;k1<inputLayers+1;k1++){ for(int k2=0;k2<hidenLayers;k2++){ VArr[k1][k2]+=(lrdotTempp[k1][k2]); //cout<<"(lrdotTempp[k1][k2]):"<<(lr*dotTempp[k1][k2])<<endl; //cout<<VArr[k1][k2]<<”*****"<<endl; } } //每训练100次预测一下准确率 if(n%10000==0){ //使用测试集验证一下准确率 //存放预测返回的结果 double resultArr[1][outputLayers]; int index; //整个样本集中预测结果正确的样本个数 int num=0; //准确率 double accuracy=0; //遍历整个测试样本 for(int k1=0;k1<dataTest.size();k1++){ vector<double>result; //取测试集中的第k1行进行测试,结果保存在resultArr中 predict(dataTest[k1],&resultArr[0][0]); //将arr转成vector arrToVector1(&resultArr[0][0],result,outputLayers);// for(int kk=0;kk<result.size();kk++){// //cout<<resultArr[0][kk]<<”%%%%%%%%"<<endl;// cout<<result[kk]<<"&&&&&&&&&7”<<endl;// } //取得结果中的最大值(概率最大)的index index=getMaxIndex(result);// cout<<”**k1:"<<k1<<endl;// cout<<"**index:"<<index<<endl;// cout<<"**Y:"<<dataTestY[k1]<<endl; if(index==dataTestY[k1]){// cout<<“k1:"<<k1<<endl;// cout<<“index:"<<index<<endl;// cout<<“Y:"<<dataTestY[k1]<<endl; num++; } } accuracy=(double)num/dataTestY.size(); //if(num>5)cout<<“num:!!!!!!!!!!!!!!!!!!!!!!!111”<<num<<endl; cout<<“epoch: “<<n<<”, “<<“accuracy: “<<accuracy<<endl; } } }};#endif训练效果 ...

April 20, 2019 · 4 min · jiezi

21天学习C语言-第五天

函数是C的核心和理念。函数的定义函数是一个有名字的独立代码块,这个代码块能完成指定的功能,有时候会返回值给调用程序。函数有唯一的名称。函数是独立的,函数能不被其他打断。函数能完成指定的功能。函数能返回值给调用程序。函数如何工作函数在被调用时执行,当一个函数被调用,调用者会把数据作为参数传给函数,参数被函数用来执行特定的逻辑,比如求平方。接下来函数内的语句开始执行,执行完毕后把所得到的结果返回给函数调用者。函数和结构化通过使用函数,可以实现结构化编程,把实现某一功能的代码独立出来,包装成函数,在需要的地方进行调用,上图的square就是这样的代码。结构化的好处把一个复杂的逻辑拆分成小的任务,代码可读性高。调试起来简单,由于函数的独立性,可以快速定位到问题的位置。增加以后的开发效率,也就是函数的可重用性,遇到重复或者相似的功能,代码可直接拿过来用。如何结构化确定要实现的目标,将目标细分为几步,每步是定为一个函数。如求平方中,总共有两步,确定要做的是求2的平方并打印出来。第一步是求平方,第二步是打印,因为打印是系统提供的函数,我们只把求的过程独立出来就好。自上而下执行函数按自上至下顺序执行(暂不说控制语句),执行完一个之后继续执行下一个。有的时候,main函数中代码量很少,仅仅是调用函数的语句。实际实现功能的代码都在各个被调用的函数当中,就行菜单一样,main函数只是相当于一个目录,真正的实现都在点击事件对应的函数当中。栗子,逻辑都在show函数中执行:写一个函数写函数最重要的就是知道想要什么,有了目标,写函数就容易许多。函数由三部分组成,函数头,函数体和函数原型。有些时候,函数原型可以省略。函数头函数头由三部分组成,类型,名称和参数。函数类型也就是函数执行后返回值的类型,可以是当前C中的任意类型比如int,char等。函数名称是函数的唯一标识,可以随意叫什么,不过最好是能见名知意。参数,多数函数都有参数,参数就是要处理的数据,参数需指明类型,多个参数用逗号隔开。函数体函数体是函数逻辑的具体实现部分,用大括号包裹,在函数头之后。一般包括局部变量声明,函数语句,和返回语句。局部变量是在函数内部声明的,用于存储当前函数的临时数据。为了可读性,局部变量尽量不要和函数外部的变量重名。函数语句是函数逻辑的具体实现部分,函数仍然可以调用其他函数。对于函数的长度,虽然没有限制,不过相对的短一些比较好。如果函数过长,那么函数执行的任务可能需要再次拆分。返回值是函数对参数执行逻辑之后得出的最终结果,返回给调用者的值。函数原型函数原型的作用是将函数的类型,名称,参数信息告诉编译器,方便编译器对函数调用做检查。实验中发现,编译器会对类型,名称做严格的检查,如果不一致就会报错。参数检查比较复杂,函数原型的参数类型和对应函数的参数类型一定要相同,个数可以相同或者函数原型不写任何参数。但是函数原型如果有参数并且和函数的参数个数不等,则会报错。函数原型可以的参数可以不写名称,或者写其他名称。调用函数在需要的位置,写被调用函数的名称并在后面添加括号,括号内为函数需要的参数,如果函数不需要参数,括号置空即可。可以将函数的返回值赋值给变量,也可以作为其他函数的参数。递归是调用函数自身,在一些特殊的运算中会经常出现递归,比如求阶乘。递归比较耗资源,非必要的时候不要用。函数位置目前阶段比较简单,代码放在一个文件中就好。实际上,函数一般会分开不同的文件放置,在需要调用的地方引入头文件(.h文件)。比如printf函数,我们引入了stdio.h之后就能用,但是printf的函数并未在我们当前的文件中定义。内联函数(Inline Functions)对于使用频繁并且体量小的函数,可以在函数前用inline关键字修饰。内联函数的作用就是在函数编译的时候将函数的内容整体复制到调用的位置,减少函数调用带来的资源消耗。很明显,内联函数会增加代码的编译时间和编译后程序的体量。inline关键字要放在函数实现部分,放在函数原型前是没有作用的。

April 19, 2019 · 1 min · jiezi

2019-03-11 PHP内存管理3笔记

2019-03-11 PHP内存管理3笔记baiyan源视频地址:http://replay.xesv5.com/ll/24…复习PHP内存分配流程利用gdb回顾PHP内存分配流程首先在_emalloc()函数处打一个断点alloc_globals是一个全局变量,可以利用AG宏访问内部字段接下来观察mm_heap中的字段:size = 0,peak = 0,表示当前没有使用任何内存real_size = 2097152 = 2M,表示当前已经向操作系统申请了一个chunk的内存跟进main_chunk字段:heap字段的地址与上述mm_heap字段的地址相同,这样可以方便快速定位到该chunk的mm_heap。注意到其地址为0x7ffff3800040,注意倒数第二位有一个4(十进制为64),即其起始地址并不是正好2MB对齐的,而是2MB+64B。这是为什么呢?看下图,该结构体前几个字段正好占用了64B,所以heap_slot字段就只能从64B的偏移量位置开始。 - next、prev字段代表chunk与chunk之间以双向链表链接,当只有一个chunk结点时,next和prev指针均指向自己 - free_pages字段为511,说明512个page中,有1个page被使用了,剩余511个空闲page - free_map字段为8个uint64_t类型,标记每个page是否被使用,共512bit。每个chunk中的512个page被分成了8组,每组64个page,正好对应free_map中的每一项,0是未使用,1是已使用 - map字段为512个uint32_t类型,共2KB。来标记是small内存还是large内存,并可以利用低位存储bit_num或已用的page数量等信息。这里它是一个large内存,有1个page已经被使用(存mm_heap)我们跟着gdb走一遍,在_emalloc()之后,调用了zend_mm_alloc_heap()函数,然后判断size的大小,这里小于了3KB(ZEND_MM_MAX_SMALL_SIZE),所以这里调用了zend_mm_alloc_small()函数,也有可能调用zend_mm_alloc_small_slow()函数,视情况而定。然后计算bin_num,这里size为11,小于64。所以直接return (size - !!size) >> 3,打印结果为1,根据其bin_num去bin_data_size数组中找到应该分配的size大小是16B,1个page可以分成256个16B,需要1个page。这样,PHP会拿出1个16B返回给用户,剩余255个16B会挂在free_slot链表上,且数组下标为1(下标为0保存8B内存,下标为1保存16B内存…),再次打印free_slot字段,观察第1个下标已经有了元素,由此可以验证我们的结论。复杂的宏替换针对视频中bin_data_size数组为什么会最终经过替换后,为什么最终bin_data_size数组只存了size一列数据的问题,为了方便讲解,在此对源码示例做了简化:#include <stdio.h>#define BIN_DATA_SIZE(num, size, elements, pages, x, y) size,#define ZEND_MM_BINS_INFO(, x, y) \ _( 0, 8, 512, 1, x, y) \ _( 1, 16, 256, 1, x, y) \ _( 2, 24, 170, 1, x, y) \ _( 3, 32, 128, 1, x, y) \ _( 4, 40, 102, 1, x, y) \ _( 5, 48, 85, 1, x, y) \ _( 6, 56, 73, 1, x, y) \ _( 7, 64, 64, 1, x, y) \ _( 8, 80, 51, 1, x, y) \ _( 9, 96, 42, 1, x, y) \ _(10, 112, 36, 1, x, y) \ _(11, 128, 32, 1, x, y) \ _(12, 160, 25, 1, x, y) \ _(13, 192, 21, 1, x, y) \ _(14, 224, 18, 1, x, y) \ _(15, 256, 16, 1, x, y) \ _(16, 320, 64, 5, x, y) \ _(17, 384, 32, 3, x, y) \ _(18, 448, 9, 1, x, y) \ _(19, 512, 8, 1, x, y) \ _(20, 640, 32, 5, x, y) \ _(21, 768, 16, 3, x, y) \ _(22, 896, 9, 2, x, y) \ _(23, 1024, 8, 2, x, y) \ _(24, 1280, 16, 5, x, y) \ _(25, 1536, 8, 3, x, y) \ _(26, 1792, 16, 7, x, y) \ _(27, 2048, 8, 4, x, y) \ _(28, 2560, 8, 5, x, y) \ _(29, 3072, 4, 3, x, y)int main() { int bin_data_size[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y) }; for (int i = 0; i < sizeof(bin_data_size) / sizeof(bin_data_size[0]); i++) { printf("%d, “, bin_data_size[i]); } //打印结果为: //{8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 448, 512, 640, 768, 896, 1024, 1280, 1536, 1792, 2048, 2560, 3072}}这样看确实很难看出来,在这个基础上,我做了一个简化版本:#include <stdio.h>#define BIN_DATA_SIZE(num, size, elements, pages, x, y) size,#define ZEND_MM_BINS_INFO(, x, y) _( 0, 8, 512, 1, x, y)int main() { int data[] = { ZEND_MM_BINS_INFO(BIN_DATA_SIZE, x, y) }; for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++) { printf("%d, “, data[i]); } //打印结果为: //{8, }}我们利用分而治之的思想,从代码层面简化问题。首先观察的顺序是由外到内,先看外面这个ZEND_MM_BINS_INFO(, x, y)宏,宏就是替换,那么我们将下划线_的位置用_BIN_DATA_SIZE这个东西替换,那么外层宏的替换结果就直接变成了_BIN_DATA_SIZE( 0, 8, 512, 1, x, y)了。而_BIN_DATA_SIZE又是一个宏,根据定义,直接替换为 size, ,注意这里第一个英文逗号(并非第二个中文逗号)也是宏替换结果的一部分,这样做是为了凑成一个数组初始化的语法。C语言数组初始化的语法为int a[2] = {1,2},由于我们简化了问题,只用了一个数组元素,在这里的替换结果就是8。那么应用到上面那个复杂版本也同理,将_BIN_DATA_SIZE这个东西替换到所有30行下划线的位置,这样最终就构成了一个由size组成的数组。那么问题来了,为什么要把如此简单的一个数组初始化问题复杂化?这样写代码真的真的不会被别人锤吗?其实源码中还有其他相关部分:#define _BIN_DATA_SIZE(num, size, elements, pages, x, y) size,static const uint32_t bin_data_size[] = { ZEND_MM_BINS_INFO(_BIN_DATA_SIZE, x, y)};#define _BIN_DATA_ELEMENTS(num, size, elements, pages, x, y) elements,static const uint32_t bin_elements[] = { ZEND_MM_BINS_INFO(_BIN_DATA_ELEMENTS, x, y)};#define _BIN_DATA_PAGES(num, size, elements, pages, x, y) pages,static const uint32_t bin_pages[] = { ZEND_MM_BINS_INFO(_BIN_DATA_PAGES, x, y)};我们可以看到,PHP源码中提供了三个类似的宏替换结构。这里的下划线_本质上就是一个占位符,也可以理解为一个函数参数,根据这个参数的不同,来返回不同的值。这个占位符的作用,就是可以根据你传入的占位符(或参数),从这个ZEND_MM_BINS_INFO这个宏中,提取出不同列的元素,并巧妙地构成了数组初始化语法。比如下划线位置替换成_BIN_DATA_SIZE,那么就是取出size这一列;如果是_BIN_DATA_ELEMENTS,就是取出elements这一列;如果是_BIN_DATA_PAGES,就是取出pages这一列。虽然这种利用了有一定技巧性的东西,在某种程度上,这样做可能会带来一点点的性能提升。但在实际开发过程中,个人觉得要尽量避免写让别人难以理解的、低可读性的代码,这样做换来的是可维护性、可扩展性的下降,这是得不偿失的。但是如果性能提升是指数这种数量级的,还是可以考虑采用的。其实这就是架构师常常需要做的一个不断权衡的过程,这就需要具体场景具体代入分析了。 ...

April 19, 2019 · 2 min · jiezi

在Linux中如何运行c++文件

在Linux中如何运行c++文件首先安装必要的工具和编译器来运行代码。gcc(GNU编译器套件)简介:GNU编译器套件(GNU Compiler Collection)包括C、C++、Objective-C、Fortran、Java、Ada和Go语言的前端,也包括了这些语言的库(如libstdc++、libgcj等等)。如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。1、 查看gcc -v命令gcc -v在Linux中如何运行c++文件若无,使用yum安装yum install gcc在Linux中如何运行c++文件2、测试根据自己喜好使用CLI/GUI编辑器中编写代码/程序。一般C程序使用扩展名.c/C ++程序使用.cpp。vim Demo.c在Linux中如何运行c++文件编译Demo.c文件gcc Demo.c -o Demo在Linux中如何运行c++文件运行可执行文件Demo./Demo打印Hello World!在Linux中如何运行c++文件欢迎大家给予宝贵的意见或者建议。欢迎大家补充或者共享一些其他的方法。感谢支持。

April 19, 2019 · 1 min · jiezi

2019-03-08 PHP内存管理2笔记

2019-03-08 PHP内存管理2笔记baiyan源视频地址:http://replay.xesv5.com/ll/24…复习宏的用法:typedef struct _zend_alloc_globals { zend_mm_heap *mm_heap;} zend_alloc_globals;…# define AG(v) (alloc_globals.v)static zend_alloc_globals alloc_globals;这个带有参数的宏取得zend_alloc_globals结构体类型里的zend_mm_heap结构体字段,如AG(mm_heap) = alloc_globals.mm_heap宏就是替换。结构体与结构体内存对齐结构体先看一段结构体代码struct.c:#include <stdio.h>int main() { struct a{ char a; int b; long c; void *d; int e; char *f; }s; s.a = ‘c’; s.b = 1; s.c = 22l; s.d = NULL; s.e = 1; s.f = &s.a; printf(“sizeof struct a is %d”, (int)sizeof(s));}编译:gcc -g struct.c -o struct运行:./struct,sizeof(a)的打印结果为:40对这个结构体进行gdb调试:在这里我们可以看到第一行是所有结构体变量的初始值,注意指针变量是一个随机的地址,在给s.d赋值的过程中,地址变成了0x0,它是一个特殊的地址值,代表NULL。除此之外,我们注意到结构体s的地址和a变量的地址是相同的。用表达式表示以上结论:&s = &s.a = f或者s = s.a = *f结构体内存对齐核心结论:它是编译器做的优化,空间换时间的思想。对齐方式:按照结构体所有字段的最小公倍数做对齐(最小公倍数是8B就按8B对齐;是4B就按4B对齐),且与结构体字段的排列顺序是相关。如图:我们利用gdb验证一下以上的结论,还是利用上述同样的代码:我们看到变量a的起始地址是150,而b的地址是154,显然做了对齐。如果不对齐,b的地址应该为151,说明a和b中间空了3B的大小。c的地址是158,就是下一个8B的起始地址,而d的地址是160(注意这里是16进制,158+8 = 160的时候才会进位),证明了c占用了8B,下面d变量也同理占用了8B。注意e变量,它是一个int,如果不对齐应该只占用4B。而它占用了170(f的起始地址)- 168(e的起始地址) = 8B,所以一定是做了内存对齐的。注意一个特例:如果b是一个char类型,那么是直接紧跟在上一个char后面,如图:注意这里的地址均为逻辑地址,每次编译后的逻辑地址是相同的,而物理地址是不同的。注意:如果调换顺序,把b和c调换位置,就会变成8B(a)+8B(c)+8B(b)+8B(d)+8B(e)+8B(f) = 48B注意:一定是所有字段的最小公倍数是几字节,就按几字节对齐,我们看一下结构体中只有char类型变量的情况:#include <stdio.h>int main() { struct a{ char a; char b; char c; }s; s.a = ‘c’; s.b = ‘b’; s.c = ‘a’; printf(“sizeof struct a is %d\n”, (int)sizeof(s));}这个结构体中只有char类型变量编译运行,输出sizeof(s)的结果为:3为什么不是4或者8?因为1,1,1的最小公倍数就是1,就按照1B来对齐,而不是4B、8B同理,如果都是int类型的变量,那么sizeof(s)的结果为12,也很好理解了联合体核心结论:所有联合体字段共用一块内存空间。整个联合体占用的空是所有字段单独占用空间大小中取占用空间大小最大的字段。同样先看一段代码:#include <stdio.h>int main() { union a{ char a; int b; long c; void *d; int e; char *f; }s; s.a = ‘c’; s.b = 1; s.c = 22l; s.d = NULL; s.e = 1; s.f = &s.a; printf(“sizeof struct a is %d”, (int)sizeof(s));}这段代码与上段结构体的代码只将struct修改为union,其他均不变编译运行,输出sizeof(a)的结果为:8我们利用gdb调试一下这段代码:我们可以看到,后面的变量一赋值,就会覆盖前面的变量值再看一下每个变量的地址,我们可以清楚地看到,所有变量的起始地址都是一样的。其他思考:一个void 类型的变量,能否直接取它的内容?答:不可以,其他有类型的指针变量可以取内容是因为记录了当前类型的长度,而void 类型没有长度,无法直接取,除非使用强制类型转换或者指定长度。延伸:PHP所有变量基于zval,zval就是由3个联合体组成(zend_value,u1,u2)这里不展开大小端:大端:也叫高尾端,即数据尾端(低位)放在高地址小端:也叫低尾端,即数据尾端(低位)放在低地址网络字节序是大端的,所以小端机器需要对收到或发出的数据包进行大小端的转换如何判断是大端还是小端:参考:判断机器大小端的两种实现方式利用指针:int为4个字节,通过强转成char类型,取低1个字节,如果恰好是0x78,那就说明低1个字节恰好存在低地址,为小端。如果取低1个字节的结果是0x12,低1个字节存在高地址,为大端。int main() { int i = 0x12345678; if ((char)&i == 78) { printf(“小端”); } else { printf(“大端”); }}利用联合体:本质思想和指针方法相同,利用了联合体共用同一块存储空间的特性。int main() { union w{ int a; char b; }c; c.a = 1; if (c.b == 1) { printf(“小端”); } else { printf(“大端”); }}利用gdb观察PHP内存分配时的情况,示例代码:<?php$a = 1;echo $a;gdb php并在_emalloc()处打断点,运行代码:我们可以清晰地看到mm_heap这个变量,它在small、large内存中存储一些额外的page分配信息等等。其中比较重要的size、peak、free_slot、main_chunk等变量。free_slot数组暂时还是空。再看mm_heap中的main_chunk字段,它来表示一个chunk,是一个双向链表。第一个字段heap是一个指向zend_mm_heap的指针,可以快速找到第一个记录信息的page,并且可以发现它的地址和上面直接打印alloc_globals.mm_heap的地址是相同的。再观察free_map字段,它由8个个uint64类型组成,代表各个page的使用情况,这里第1个page存了zend_mm_heap结构体,已经被使用。再看map字段,它是一个数组,大小为512,每个都是uint32类型,打印数组第一项的值,以16进制表示为0x40000001,代表large内存,最后一位是1,代表分配1个page。使用c命令继续运行:我们可以看到free_slot上有3项已经不是0x0了,说明上次分配过small内存。且现在free_map的值发生了变化,而map说明第1、2、3、4、9页已经被使用了,那么为什么中间有几个0呢,是因为第4个map值,按照16进制打印为0x40000005,是large内存,且需要分配5个页,所以后面的4个页都是0。 ...

April 18, 2019 · 1 min · jiezi

C语言最难啃的三块硬骨头

C语言最难啃的三块硬骨头提到C语言很多初学者都觉得,学到中间就进行不下去了,因为碰到了几个硬骨头死活翻不过去,于是很多人给C语言下结论太难了,太靠近底层了,特别是那几块难啃的骨头,直接理解不了,进行不下去。如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。今天就来说下,最难啃的三块骨头,看到底是谁?C语言最难啃的三块硬骨头内存布局指针公认最难理解的概念,也是让很多初学者选择放弃的直接原因指针之所以难理解,因为指针本身就是一个变量,是一个非常特殊的变量,专门存放地址的变量,这个地址需要给申请空间才能装东西,而且因为是个变量可以中间赋值,这么一倒腾很多人就开始犯晕了,绕不开弯了。C语言之所以被很多高手所喜欢,就是指针的魅力,中间可以灵活的切换,执行效率超高,这点也是让小白晕菜的地方。指针是学习绕不过去的知识点,而且学完C语言,下一步紧接着切换到数据结构和算法,指针是切换的重点,指针搞不定下一步进行起来就很难,会让很多人放弃继续学习的勇气。指针直接对接内存结构,常见的C语言里面的指针乱指,数组越界根本原因就是内存问题。在指针这个点有无穷无尽的发挥空间。很多编程的技巧都在此集结。指针还涉及如何申请释放内存,如果释放不及时就会出现内存泄露的情况,指针是高效好用,但不彻底搞明白对于有些人来说简直就是噩梦。C语言最难啃的三块硬骨头函数概念,面向过程对象模块的基本单位,以及对应各种组合,函数指针,指针函数一个函数就是一个业务逻辑块,是面向过程,单元模块的最小单元,而且在函数的执行过程中,形参,实参如何交换数据,如何将数据传递出去,如何设计一个合理的函数,不单单是解决一个功能,还要看是不是能够复用,避免重复造轮子。函数指针和指针函数,表面是两个字面意思的互换实际上含义截然不同,指针函数比较好理解,就是返回指针的一个函数,函数指针这个主要用在回调函数,很多人觉得函数都没还搞明白,回调函数更晕菜了。其实可以通俗的理解指向函数的指针,本身是一个指针变量,只不过在初始化的时候指向了函数,这又回到了指针层面。没搞明白指针再次深入的向前走特别难。C语言最难啃的三块硬骨头结构体,递归很多在大学学习C语言的,很多课程都没学完,结构体都没学到,因为从章节的安排来看好像,结构体学习放在教材的后半部分了,弄得很多学生觉得结构体不重要,如果只是应付学校的考试,或者就是为了混个毕业证,的确学的意义不大。如果想从事编程这个行业,对这个概念还不了解,基本上无法构造数据模型,没有一个业务体是完全使用原生数据类型来完成的,很多高手在设计数据模型的时候,一般先把头文件中的结构体数据整理出来。然后设计好功能函数的参数,以及名字,然后才真正开始写c源码。如果从节省空间考虑结构体里面的数据放的顺序不一样在内存中占用的空间也不一样,结构体与结构体之间赋值,结构体存在指针那么赋值要特别注意,需要进行深度的赋值。C语言最难啃的三块硬骨头递归一般用于从头到位统计或者罗列一些数据,在使用的时候很多初学者都觉得别扭,怎么还能自己调用自己?而且在使用的时候,一定设置好跳出的条件,不然无休止的进行下去,真就成无线死循环了。这三大块硬骨头是学习C语言的绊脚石,下功夫拿掉基本上C语言的大动脉就打通了,那么再去学习别的内容就相对比较简单了。编程学习过程中越是痛苦的时候,学到的东西就会越多,克服过去就会自己的技能,放弃了前面的付出的时间都将清零。越是难学的语言在入门之后,在入门之后越觉得过瘾,而且还容易上瘾。你上瘾了没?

April 17, 2019 · 1 min · jiezi

快速排序(QuickSort) 算法思路详解

最近在学算法,学到快速排序心得就和大家分享一下。以下代码为c做演示,看不懂代码不要紧,做参考就好了,主要为了明白快速排序思路。希望能帮助到大家。快速排序分为4个步骤找一个基准数(参照数)从右往左找比基准数小的数与左坐标交换从左往右找比基准数大的数与右坐标交换左、右坐标相遇时,基准数与相遇坐标交换文字描述已讲述完,接下来草稿演示,也可以直接向下翻看代码,可能代码更有说服力国足有6名队员从左到右身高排序球号为: 5号 9号 12号 3号 7号 8号教练规定左边第一个最高的为队长(基准数):5号教练要拆分两个队:右边的球号要小于队长球号并且左边球号不能大于右边 找到后和左边交换位置找到了3号比5号小,与左队当前坐标交换位置 新顺序为:3号 9号 12号 5号 7号 8号 坐标定在第4个人左边的球号要大于队长球号并且左边球号不能大于右边找到了9号比5号大,与右队当前坐标交换位置 新顺序为:3号 5号 12号 9号 7号 8号 坐标定在第2个人然后右边继续走找比5小的,最后走到了左队坐标位置,两个相遇相等的坐标让队长(基准数)放到相遇的位置。排队完成。最终得到的顺序为:3号 5号 12号 9号 7号 8号左队都是比队长(5号)球号小的,右边都是比队长大的接下来就要从队长开始继续拆分左、右边和上面教练说的一样直到不能拆分为止整个思路基本就这样,接下来代码实现:#include <stdio.h>#include <stdlib.h>int a[101],n;/*** 快速排序:* 1.以左边第一位作为基准数,* 2.先右边找一个比基准数小的数与左指针所在的位置进行交换 * 3.从左边找一个比基准数大的数与右指针所在的位置进行交换* 4.相遇后则把基准数和左、右指针重合的位置进行交换* 5.重复1/2/3/4操作* 注意:* 如果左坐标大于右坐标位置则无法计算,必须左坐标小于右坐标。*/void quicksort(int left, int right){ int i,j,base; i = left; j = right; if (left>right) return ; // 1.第一步,定义基准数 base = a[left]; // 4.左坐标不等于右坐标时继续迭代,等于则相遇 while(left != right){ // 2.如果右边大于基准数则继续递减迭代至小于基准数停止 while(left<right && a[right]>=base) right–; // 2.1 与左指针所在位置进行交换 a[left] = a[right]; // 3.如果左边小于基准数则继续递增迭代至大于基准数停止 while(left<right && a[left]<=base) left++; // 3.1 与右指针所在位置进行交换 a[right] = a[left]; } // 4.1 左右坐标相遇替换基准数 a[left] = base; // 5.重复1.2.3.4 递归 quicksort(i, left-1); // 重复基准数左边 quicksort(left+1, j); return;}int main(){ int i,j; printf("=============快速排序==============\r\n"); printf(“请输入数量:”); scanf("%d", &n); for (i=1; i<=n; i++){ scanf("%d", &a[i]); } quicksort(1, n); printf(“快速排序结果:\r\n”); for (i=1; i<=n; i++) printf("%d “, a[i]); getchar(); getchar(); system(“pause”); return 0;}本文做为技术参考,也欢迎大神们指导和批评,希望对大家有帮助。 ...

April 17, 2019 · 1 min · jiezi

Swoole协程之旅-前篇

写在最前 Swoole协程经历了几个里程碑,我们需要在前进的道路上不断总结与回顾自己的发展历程,正所谓温故而知新,本系列文章将分为协程之旅前、中、后三篇。前篇主要介绍协程的概念和Swoole几个版本协程实现的主要方案技术;中篇主要深入Zend分析PHP部分的原理和实现;后篇主要补充和分析协程4.x的实现。软文正式开始协程是什么? 概念其实很早就出现了,摘wiki一段:According to Donald Knuth, the term coroutine was coined by Melvin Conway in 1958, after he applied it to construction of an assembly program.The first published explanation of the coroutine appeared later, in 1963. 协程要比c语言的历史还要悠久,究其概念,协程是子程序的一种, 可以通过yield的方式转移程序控制权,协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。协程完全有用户态程序控制,所以也被成为用户态的线程。协程由用户以非抢占的方式调度,而不是操作系统。正因为如此,没有系统调度上下文切换的开销,协程有了轻量,高效,快速等特点。(大部分为非抢占式,但是,比如golang在1.4也加入了抢占式调度,其中一个协程发生死循环,不至于其他协程被饿死。需要在必要的时刻让出CPU,Swoole在V4.3.2增加了这个特性)。 协程近几年如此火爆,很大一部分原因归功与golang在中国的流行和快速发展,受到很多开发的喜爱。目前支持协程的语言有很多,例如: golang、lua、python、c#、javascript等。大家也可以用很短的代码用c/c++撸出协程的模型。当然PHP也有自己的协程实现,也就是生成器,我们这里不展开讨论。Swoole1.x Swoole最初以高性能网络通讯引擎的姿态进入大家视线,Swoole1.x的编码主要是异步回调的方式,虽然性能非常高效,但很多开发都会发现,随着项目工程的复杂程度增加,以异步回调的方式写业务代码是和人类正常思维相悖的,尤其是回调嵌套多层的时候,不仅开发维护成本指数级上升,而且出错的几率也大幅增加。大家理想的编码方式是:同步编码得到异步非阻塞的性能。所以Swoole很早的时候就开始了协程的探索。 最初的协程版本是基于PHP生成器GeneratorsYield的方式实现的,可以参考PHP大神Nikita的早期博客的关于协程介绍。PHP和Swoole的事件驱动的结合可以参考腾讯出团队开源的TSF框架,我们也在很多生产项目中使用了该框架,确实让大家感受到了,以同步编程的方式写异步代码的快感,然而,现实总是很残酷,这种方式有几个致命的缺点:所有主动让出的逻辑都需要yield关键字。这会给程序员带来极大的概率犯错,导致大家对协程的理解转移到了对Generators语法的原理的理解。由于语法无法兼容老的项目,改造老的项目工程复杂度巨大,成本太高。这样使得无论新老项目,使用都无法得心应手。Swoole2.x 2.x之后的协程都是基于内核原生的协程,无需yield关键字。2.0的版本是一个非常重要的里程碑,实现了php的栈管理,深入zend内核在协程创建,切换以及结束的时候操作PHP栈。在Swoole的文档中也介绍了很多关于每个版本实现的细节,我们这篇文章只对每个版本的协程驱动技术做简单介绍。原生协程都有对php栈的管理,后续我们会单独拿一片文章来深入分析PHP栈的管理和切换。 2.x主要使用了setjmp/longjmp的方式实现协程,很多C项目主要采用这种方式实现try-catch-finally,大家也可以参考Zend内核的用法。setjmp的首次调用返回值是0,longjmp跳转时,setjmp的返回值是传给longjmp的value。 setjmp/longjmp由于只有控制流跳转的能力。虽然可以还原PC和栈指针,但是无法还原栈帧,因此会出现很多问题。比如longjmp的时候,setjmp的作用域已经退出,当时的栈帧已经销毁。这时就会出现未定义行为。假设有这样一个调用链:func0() -> func1() -> … -> funcN()只有在func{i}()中setjmp,在func{i+k}()中longjmp的情况下,程序的行为才是可预期的。Swoole3.x3.x是生命周期很短的一个版本,主要借鉴了fiber-ext项目,使用了PHP7的VM interrupts机制,该机制可以在vm中设置标记位,在执行一些指令的时候(例如:跳转和函数调用等)检查标记位,如果命中就可以执行相应的hook函数来切换vm的栈,进而实现协程。虽然我们完整的实现了协程的功能,但是由于并没有相对2.x有很大的进步,原因我们后续的文章会做进一步分析,所以我们放弃了这个版本,直接进入了4.x的版本迭代。Swoole4.x4.x协程是当前Swoole的协程版本,借鉴了前面版本的缺点和问题,引入了PHP+C双栈管理维护,完美的支持PHP各种语法,详细分析我们放在系列文章最后。End协程之旅前篇结束,下一篇文章我们将深入Zend分析Swoole原生协程PHP部分的实现。

April 16, 2019 · 1 min · jiezi

为什么很多人吐槽谭浩强的C语言程序设计?

这个观点在网络上已经论战了很长时间,出现这种情况还是历史遗留问题,老谭出的C语言教材几乎是国内的第一版,由于中英翻译问题或者对编程文化理解的差异性导致出来的书多多少少存在一些纰漏。不能只是记住其缺点,老谭书最主要在那个年代起到了普及C语言的作用,这已经足够了,如同做一个产品,首先解决的是有没有问题,最后才是细节完善阶段。最后,如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。为什么很多人吐槽谭浩强的C语言程序设计?如同历史上发生了许多在今天看来很搞笑的事情,社会在进步每个阶段必然有其特殊的产物,现在的C语言教材各种特点的都有,还有直接从外国翻译的经典书籍,同时也把国内很多的C语言学习人的口味给调动起来了,更可恨的是很多老程序员是看了老谭的教材学出来的,回头又对这个教材产生不满,吃水不忘挖井人,在编程书籍匮乏的年代,能有一本书解决大家的燃眉之急,可以没有感激之情,起码不能落井下石。其实这本书典型的中国特色思想的产物,开始上来出来了很多理论,很多人直接被这些理论给绕晕了,但是一旦跨越过去后面显得简单了许多,典型中国式的先苦后甜式的教程。为什么很多人吐槽谭浩强的C语言程序设计?为什么这么多人吐槽老谭这本书?1.这本书本身存在很多语法错误,这种错误主要是理解或者版本更迭没有及时更新造成,中国大学里面很多教材更迭相对偏慢。2.现在有很多种无论从内容质量还是排版形式都相比老谭的高明不少,没有对比就没有伤害,由于更新比较慢差距还是相当明显的,很多人还不解的问,都这么陈旧了为啥还拿出来作为教材使用,其实很多槽点都来源于此。3.大学里面很多设施特别是实验室里的C语言的编译环境还有很多是turbo c很多学生看到网上的评论顿时觉得low了许多,计算机等级考试的题目也还是老谭老版本书籍出的,里面的确存在很多和现在语法相悖的地方,在现在信息传递如此发达的今天,吐槽的力度以及影响程度都是空前的。为什么很多人吐槽谭浩强的C语言程序设计?无论怎么吐槽,一个人编程水平的高低和所在大学,使用的什么教材都没有太直接的关系,很多人在网上买编程教材一定想着买个最新版本的,其实完全没有必要,这些都是客观因素,主观因素才是关键,真要想学的好,什么教材都玩的转,取决于内在的意识形态,驱动力不够有再好的教材也是浪费资源,无论怎么说老谭对于中国计算机行业的贡献还是非常突出的,感激的心情更多点。现在还有很多的论调包含的大致意思就是C语言现在已经不行了,完全没有必要再去学习了,看看招聘职位关于C语言的真是少的可怜,现在明显的都在向高级语言进发了,大趋势是向集成化程度高的语言前进,但是集成化语言本身很多都是C语言完成的,未来可能C语言学习的会变少,但是职位要求以及薪资水平会高的让普通程序员都只有羡慕的份,物以稀为贵,这也是市场规模。踏踏实实学好正在学习的编程语言,把精力放在语言本身,而不是去争论谁好谁坏,即使分出个高低了对于个人又有什么直接好处,认准一种编程语言一直学到最后,然后想办法过度到别的语言,触类旁通。

April 16, 2019 · 1 min · jiezi

Mac上的gdb之:从入门到放弃

副标题:Mac上的gdb无法正常调试的问题Mac上用brew install gdb安装gdb后,无法正常的运行run命令,报错如下:(gdb) break mainBreakpoint 1 at 0x100000f66: file a.c, line 4.(gdb) runStarting program: /Users/solomonxie/Workspace/tests/clang/aUnable to find Mach task port for process-id 63414: (os/kern) failure (0x5). (please check gdb is codesigned - see taskgated(8))这个不是c程序的问题,也不是gdb的问题,而是Mac的问题。参考:gdb doesn’t work on macos High Sierra 10.13.3为什么Mac不能调试?“因为 Darwin 内核在你没有特殊权限的情况下,不允许调试其它进程。调试某个进程,意味着你对这个进程有完全的控制权限,所以为了防止被恶意利用,它是默认禁止的。允许 gdb 控制其它进程最好的方法就是用系统信任的证书对它进行签名。“参考:gdb fails with “Unable to find Mach task port for process-id” error参考:How to install and codesign GDB on OS X El Capitan具体步骤如下:开启root权限用Spotlight搜索Directory Utility程序,打开后,点击左下角解锁,然后打开菜单->Edit->Enable root user->创建密码。修改/System/Library/LaunchDaemon/com.apple.atrun.plist文件将第22行的-s改为-sp然后保存退出。一般来讲管理员是没有权限修改的,所以需要重启进入“安全模式”用root权限解开系统文件的保护,再重启,修改文件,再重启进入安全模式,再开启系统文件保护,再重启回到正常系统。步骤为:重启,黑屏时按住Ctrl-r不松手一直到苹果标志出现。进入安全模式后,打开菜单Utilities-Terminal终端,输入csrutils disable解锁系统文件保护。然后重启,回到正常系统中,sudo vim /System/Library/LaunchDaemon/com.apple.atrun.plist将文件中22行-s改为-sp,保存退出。重启再次进入安全模式,命令行输入csrutils enable锁定系统文件保护。再重启,回到正常系统,进行下一步。删除所有现有的gdb版本:brew uninstall –force gdb打开系统的Applications -> Utilities -> Keychain Access删除所有gdb相关的证书。重新安装gdb:brew install gdb创建证书打开系统keychain管理器:Keychain Access, go to menu Keychain Access-> Certificate Assistant -> Create a Certificate。创建新的证书,所填内容如下:Name : gdb-certIdentity Type: Self Signed RootCertificate Type : Code Signing[X] Let me override defaultsSerial Number : 1Validity Period (days): 3650Key Size : 2048Algorithm : RSA[X] Include Key Usage Extension[X] This extension is criticalCapabilities:[X] Signature[X] Include Extended Key Usage Extension[X] This extension is criticalCapabilities:[X] Code Signing[X] Include Subject Alternate Name ExtensionKeychain: System为证书添加信任在Keychain管理器里,双击刚刚创建好的证书,在Trust中全部选择为Always Trust:重启taskgated并codesign将程序与证书关联再打开命令行输入:sudo killall taskgatedcodesign -fs “gdb-cert” which gdblaunchctl load /System/Library/LaunchDaemons/com.apple.taskgated.plist设置set startup-with-shell off进入gdb调试程序,然后输入命令:(gdb) set startup-with-shell off然后正式开始调试。如果调试没有问题,则将set startup-with-shell off这句话写入~/.gdbinit文件中,长久生效。如果经历了这一切都没用,那么试试自己编译第三方gdb因为看到有人是由于更新了gdb或更新了os系统后才遇到问题,所以想是不是gdb版本与当前os版本不合的问题。所以决定自己编译别的版本gdb。官方各个版本的下载地址:https://ftp.gnu.org/gnu/gdb/(经过测试,我的在MacOS 10.12 Sierra上编译各个新老版本gdb都编译不成功)开始下载编译:cd /tmpwget https://ftp.gnu.org/gnu/gdb/gdb-7.12.1.tar.gzcd gdb-*/./configure –prefix=/opt/gdb-7.12 && echo [ OK ]make && echo [ OK ]sudo make install && echo [ OK ]如果还是没用,那么需要针对自己的OS版本做调查了我当前的系统是MacOS 10.12 Sierra。相关的说法是:“None GDB 7.11 or 7.12.1 will not work on Sierra 10.12.4 In short it’s because of Apple security upgrade. We need to wait for re-enabling when some new version will shows up.“顺着这条思路搜索,找到一个有人已经编译好的gdb二进制单文件。然后再用codesign给它签名,竟然就可以用了!在这里下载gdb_7.12.1_ sierra .zip或在百度网盘下载。解压后,备份并替换本机的gdb,放到/usr/local/bin/中。然后pkill taskgated并codesign -s gdb-cert /usr/local/bin/gdb进行签名。但是直接gdb还不行,需要用sudo gdb ..才能正常用。注意:重新安装gdb后。第三方软件如cgdb,需要重新安装才能使用,否则完全无法用。最后的最后Mac上LLDB才是王道。Xcode默认调试器是LLDB,说明了苹果不鸟GNU。也有人说,GDB是过去,LLDB是将来。虽然不一定正确,但也证明了LLDB也很强大。再有一点最重要的理由:你的项目生产环境真的是在Mac上吗?既然生产环境不在Mac,为什么要用Mac编译?这个逻辑一想通,就全通了—— 一般生产环境是在Linux服务器上的,所以你大可以共享项目文件夹给服务器,然后SSH进服务器进行编译调试。如果只是学习语言用的小文件,那么更没必要用到强大的GDB功能,在Mac本地用LLDB即可。所以,唯一的缺点就是用不了各类GDB的衍生品、GUI一类,排除这点,还是安心用LLDB吧,不要在Mac上折腾GDB了。。。 ...

April 16, 2019 · 2 min · jiezi

21天学习C语言-第四天

语句,表达式和运算符。语句(Statements)C中语句指的是一个让计算机执行某任务的完整指令,一般来说一行为一个语句,以分号结尾。栗子:x = 2 + 3; //计算机执行2+3并存到x中,一个简单的语句以下各写法,操作是一样的:x=2+3; //变量和操作数之间没有空格x = 2 + 3; //变量和操作数之间有空格x =2+3; //变量和操作数不在一行C不关心语句中的空格,编译器只识别字符,会自动忽略空格。处于可读性的考虑,建议使用第二种写作方式。编译器并不会忽略常量中的空格,比如:“HELLO WORLD, NICE TO MEET YOU!” //单词之间的空格不会被忽略掉特殊情况也存在多行为一个语句,并且有不以分号结尾的语句。例子:// 不以分号结尾#include<stdio.h>#define PI 3.14// 一个语句折行,虽然不好看,但是合法printf( “hello world!”);// 在字符串中直接折行是不合法的printf(“Hello, world!”);// 如果需要折行,在折行处添加\printf(“Hello,\ world!”);空语句(Null Statements)就是什么也不做的语句,直接放置一个分号;,表示当前行的代码不做任何操作。复合语句(Compound Statements)是多个语句在一起组成的一个语句块,差不多所有的程序中都包含了复合语句,例子:{ x = 2; y = x + 1;}表达式(Expressions)表达式是计算数值的任何东西(按原文直译),感觉就是数据本身,和存储数据的变量,常量都是表达式,同时也包含数据计算的操作。简单表达式(Simple Expressions)是单一组成,比如变量,常量等。PI // 常量0 // 数字name // 变量复杂表达式(Complex Expressions)与单一相对,由多个单一组成。x + yz = x + 10;运算符(Operators)运算符是命令计算机对操作数执行对应操作的符号。赋值运算符(Assignment Operator)赋值运算符就是等号(=),把一个值给一个变量。x = y数学运算符(Mathematical Operators)数学运算符顾名思义,进行数学运算的符号。C中数学运算包括两个一元运算符(Unary Mathematical Operators)和5个二元运算符(Binary Mathematical Operators)。一元运算符表示操作数只有一个的运算符,既++和–,也就是大家熟知的自增和自减运算。int x = 10, y = 9;x ++; // 11y –; // 8二元运算符操作数有两个,包括加(+)减(-)乘(*)除(/)和求余(%)。int x = 10, y = 5, z;z = x + y; // 15z = x - y; // 5z = x * y; // 50z = x / y; // 2z = x % y; // 0运算符的优先级(Precedence)这里的优先级基本遵循了数学运算的优先级,括号最好,其次是乘除,最后是加减。x = 4 + 5 * 3; //19比较特殊的是++,–和%,++,–的优先级高于乘除,求余和乘除相同。z = 12 % 5 * 2; // 4, 由于是同级,从左到右计算,先求余,然后相乘int x = 5, y;y = 2 * ++x; //12, ++优先计算如果是嵌套括号,则内部的优先级高于外部,由内到外依次计算。x = 25 - (2 * (5 + (4 / 2))); // 11关系运算符(Relational Operators)关系运算符就是我们常用的比较大小,是否相等的运算符,常见于if语句中。包括等于(==),大于(>),小于(<),大于等于(>=),小于等于(<=)和不等于(!=)。if (age>=18) { printf("%s", “成年人!”); }if语句if语句用来控制程序的运行。可单一使用if结构,也可与else或者else if一起使用。整个if语句结构执行完毕之后,继续执行if结构之后的代码。// if的执行部分不用大括号包裹,为了可读性,不建议这样写if (x > y) y = x;// 推荐if (x > y) { y = x;}// else ifint x = 10;if (x > 10) { printf(“x大于10”);}else if(x > 5){ printf(“x大于5”);} else{ printf(“x小于5”);}printf(“if语句执行完毕”);计算关系表达式(Evaluating Relational Expressions)有if的地方就有true和false,true和false对应到数字就是1和0。a = (5 == 5); // a等于1a = (5 != 5); // a等于0另外,不等于0的数字都认为是true,包括负数。有些人认为0是分界线,大于0才是true,这是不正确的。-1做为if的条件,是按true进行处理的。关系运算符优先级在if条件中,一般不只有关系运算,还包含数学运算,数学运算的优先级高于关系运算。if (x + 2 > y) //先算x + 2等于和不等于的优先级低于大于小于的比较。x == y > z //先计算y > z逻辑运算符(Logical Operators)逻辑运算符有三个,与(&&)或(||)非(!)。x > 2 && y < 5 //两个条件同时成立时是truex > 2 || y < 5 //两个条件有一个成立时是true!(x > 2) // 条件不成立时是true逻辑运算符的优先级比较低,之前提到的优先级都高于逻辑运算符,! > && > ||。符合赋值运算符(Compound Assignment Operators)符合赋值运算符是当某个变量自身进行数学运算时的一种简化写法。如:x = x + 5;x += 5;条件运算符(Conditional Operator)条件运算符是简单if的一种简化,常用于二选一的时候。例子:int x = 5;int y = x > 0 ? 1 : 0;当x大于5的时候y等于1,否则y等于0。内容不复杂,但作者的讲解很详细,篇幅很大! ...

April 14, 2019 · 2 min · jiezi

C++和Java互怼对方是烂语言,PHP怎么就中枪了?

当编程语言们,都加入了一个微信群,他们会聊什么?这个脑洞还在继续……前情回顾:当编程语言们加入了一个微信群,他们会聊什么?最后,如果大家如果在自学遇到困难,想找一个C++的学习环境,可以加入我们的C++学习圈,点击我加入吧,会节约很多时间,减少很多在学习中遇到的难题。C++和Java互怼对方是烂语言,PHP怎么就中枪了?编程语言之争,在技术圈一直是颇有争议的问题,以前只要是“世界上最好的编程语言”之争,自从PHP一统天下后,只要一提PHP,所有人的枪口都会朝向他,从而争吵避免了……C++和Java互怼对方是烂语言,PHP怎么就中枪了?许多新人喜欢加入一些技术交流群,这是热爱学习的表现,但事实上技术群到最后通常只有两种命运:变成闲聊群,或者死群,红包都拯救不了。如果你想学习技术,可以上GitHub,stackoverflow,当然也可以来上W3Cschool。C++和Java互怼对方是烂语言,PHP怎么就中枪了?汇编语言一直在计算机界占有重要的地位,但其学习难度极高,懂汇编的程序员只占了很小一部分。C++和Java互怼对方是烂语言,PHP怎么就中枪了?最近,马云发表了自己对996的看法。但有网友挖出2017年马云接受韩国媒体采访的视频。视频中马云说道:如果有来生,我一定选择家庭,我后悔终日忙工作,根本没时间陪陪家人。但在马云最新发表对996的看法时,画风突变:“我很幸运,我没有后悔12x12,我从没有改变过自己这一点。”看来谁也逃不过真香定律。

April 14, 2019 · 1 min · jiezi

21天学习C语言-第三天

数据的存储,变量和常量。程序存在的意义就是数据处理,计算机通常需要将要处理的数据存储起来。存储方式有两种,存储在变量或者常量中。之前提过,变量的值是可变的,常量的值是不可变的。计算机内存计算机使用随机访问内存(random-access memory,RAM)来存储处理过程中的数据。RAM中的数据是不稳定的,如果需要,你可以改变所存储的信息。同时,RAM中的数据也不持久,一旦关机,数据就丢失了。计算机内存一般以MB为单位,1MB=1024KB, 1KB=1024B, B为byte的缩写。 byte是计算机数据存储的基础单元。RAM是按照顺序排列的,一个byte接着另一个。每一个byte都有一个唯一的地址来标识,我们用这个地址来找内存中的某一个byte。这些地址按顺序分配给RAM,从0开始,一直到系统的最大限制,目前还不需要手动操作内存,都是系统自动搞定的。变量(Variables)变量是数据在内存中存储位置的名称,程序通过变量来找到数据的所在位置。变量命名原则变量名称可以包含字母,数字,下划线变量首位必须是字母,下划线也可以,但是不推荐,不能以数字开头。C语言是大小写敏感的,name和Name是两个完全不同的变量。关键字不能作为变量名。变量命名要见名知意,可以下划线分隔单词,也可以用驼峰命名法,看个人喜好。下划线可读性高一些,驼峰写代码的时候速度快一些。数值类型变量C为变量提供了不同的数值类型,不同的类型在内存中有不同的存储要求,在做运算时的效率也不一样。选择合适的类型,能有效提高程序的运算效率。具体有以下两种数字类型:整型(Integer)为没有小数部分的数字,又包括有符号(signed)和无符号(unsigned)。有符号包括正数附属和0,无符号则是非负数。浮点型(Floating-point)为有小数部分的数字。下图是变量的类型,关键字,长度及取值范围(一直以为char不是数值类型,这里不太理解)。变量声明C语言中,变量在使用之前必须声明,声明变量是告诉编译器变量的名字和类型,有的时候会直接初始化变量的值。如果使用未声明的变量,编译器会报错。变量声明格式如下:typename variable_name;typename是前面变量类型的关键字variable_name是变量的名字例如:int age; //年龄,整型变量float salary = 30000.00; //薪水,浮点型变量,初始化为30000.00typedef关键字typedef用来给一个已存在的类型关键字重新命名,暂时感觉不到有什么用。不过在刚毕业的时候读过一篇文章,如何写一些谁都不能维护的代码,里边经常用这个关键字。看下例子:typedef int integer;integer count;这里把int这个关键字重命名为integer,然后用integer这个关键字声明了变量count。变量初始化前文提过,使用变量之前要先声明,这样编译器才能不报错。同时,使用变量之前也要初始化,否则,变量当前的值可能是不确定。如图,每次输出未赋值的变量,结果都是不一样的:C中用=表示赋值操作,如:int count;count = 18;声明整型变量count,之后给count赋值18.常量(Constants)常量和变量一样,都是用来存储数据的,不同的是,常量中存储的数据在程序执行过程中是不能改变的。常量分为字面常量(Literal Constants)和符号常量(Symbolic Constants)。字面常量字面常量是在需要的地方直接写在代码里的,没有什么特殊的深意。例如:int count = 20;float tax_rate = 0.28;这里的20和0.28都是字面常量,在需要的地方直接使用就可以了。符号常量符号常量是把一个字符常量用一个名称展现(不太好表达)出来,简单来说调用的时候用名称就行了。经典例子:float circumference = 3.14 * (2 * radius); float area = 3.14 * (radius)(radius);这里我们计算了圆的周长和面积,同时都用到了3.14这个常量,如果我们要继续计算其他用到的面积,那么就需要一直写3.14这个常量了。这时候符号常量就有用了,我们把3.14这个数字定义为符号常量PI。之后的代码就可以简化了:float circumference = PI * (2 * radius); float area = PI * (radius)(radius);有人说这其实没有区别呀,每个地方写PI和每个地方写3.14差别不大。差别并不在意写代码时是否多按了几个键,试想下,如果这时候要求结果精确一些,需要把3.14换成3.1415926的时候,麻烦的事就来了。如果定义了常量PI,那么我们直接在代码里把PI的值改为3.1415926,一键搞定了全部的3.14。定义符号常量常量名习惯上都要大写,这样容易和其他变量区分开来。定义符号常量有两种方式,#define和const:#define定义常量格式如下,关键字+常量名+常量,不需要以分号结尾:#define CONSTANT_NAME literal#define PI 3.14const定义常量格式,关键字+类型+常量名=常量;,分号结尾:const type CONSTANT_NAME = literal;const float PI = 3.14;两种定义常量的方式是有区别的,会在后续的学习中逐渐解释。第三天就明显感觉赋值了许多,洗洗睡了。

April 14, 2019 · 1 min · jiezi

avformat_open_input

声明/** * Open an input stream and read the header. The codecs are not opened. * The stream must be closed with avformat_close_input(). * * @param ps Pointer to user-supplied AVFormatContext (allocated by avformat_alloc_context). * May be a pointer to NULL, in which case an AVFormatContext is allocated by this * function and written into ps. * Note that a user-supplied AVFormatContext will be freed on failure. * @param url URL of the stream to open. * @param fmt If non-NULL, this parameter forces a specific input format. * Otherwise the format is autodetected. * @param options A dictionary filled with AVFormatContext and demuxer-private options. * On return this parameter will be destroyed and replaced with a dict containing * options that were not found. May be NULL. * * @return 0 on success, a negative AVERROR on failure. * * @note If you want to use custom IO, preallocate the format context and set its pb field. */int avformat_open_input(AVFormatContext **ps, const char *url, AVInputFormat *fmt, AVDictionary **options);定义int avformat_open_input(AVFormatContext **ps, const char *filename, AVInputFormat *fmt, AVDictionary **options){ AVFormatContext *s = *ps; int i, ret = 0; AVDictionary *tmp = NULL; ID3v2ExtraMeta *id3v2_extra_meta = NULL; if (!s && !(s = avformat_alloc_context())) return AVERROR(ENOMEM); if (!s->av_class) { av_log(NULL, AV_LOG_ERROR, “Input context has not been properly allocated by avformat_alloc_context() and is not NULL either\n”); return AVERROR(EINVAL); } if (fmt) s->iformat = fmt; if (options) av_dict_copy(&tmp, options, 0); if (s->pb) // must be before any goto fail s->flags |= AVFMT_FLAG_CUSTOM_IO; if ((ret = av_opt_set_dict(s, &tmp)) < 0) goto fail; av_strlcpy(s->filename, filename ? filename : “”, sizeof(s->filename)); if ((ret = init_input(s, filename, &tmp)) < 0) goto fail; s->probe_score = ret; if (!s->protocol_whitelist && s->pb && s->pb->protocol_whitelist) { s->protocol_whitelist = av_strdup(s->pb->protocol_whitelist); if (!s->protocol_whitelist) { ret = AVERROR(ENOMEM); goto fail; } } if (!s->protocol_blacklist && s->pb && s->pb->protocol_blacklist) { s->protocol_blacklist = av_strdup(s->pb->protocol_blacklist); if (!s->protocol_blacklist) { ret = AVERROR(ENOMEM); goto fail; } } if (s->format_whitelist && av_match_list(s->iformat->name, s->format_whitelist, ‘,’) <= 0) { av_log(s, AV_LOG_ERROR, “Format not on whitelist '%s'\n”, s->format_whitelist); ret = AVERROR(EINVAL); goto fail; } avio_skip(s->pb, s->skip_initial_bytes); / Check filename in case an image number is expected. / if (s->iformat->flags & AVFMT_NEEDNUMBER) { if (!av_filename_number_test(filename)) { ret = AVERROR(EINVAL); goto fail; } } s->duration = s->start_time = AV_NOPTS_VALUE; / Allocate private data. */ if (s->iformat->priv_data_size > 0) { if (!(s->priv_data = av_mallocz(s->iformat->priv_data_size))) { ret = AVERROR(ENOMEM); goto fail; } if (s->iformat->priv_class) { *(const AVClass *) s->priv_data = s->iformat->priv_class; av_opt_set_defaults(s->priv_data); if ((ret = av_opt_set_dict(s->priv_data, &tmp)) < 0) goto fail; } } / e.g. AVFMT_NOFILE formats will not have a AVIOContext */ if (s->pb) ff_id3v2_read_dict(s->pb, &s->internal->id3v2_meta, ID3v2_DEFAULT_MAGIC, &id3v2_extra_meta); if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->iformat->read_header) if ((ret = s->iformat->read_header(s)) < 0) goto fail; if (!s->metadata) { s->metadata = s->internal->id3v2_meta; s->internal->id3v2_meta = NULL; } else if (s->internal->id3v2_meta) { int level = AV_LOG_WARNING; if (s->error_recognition & AV_EF_COMPLIANT) level = AV_LOG_ERROR; av_log(s, level, “Discarding ID3 tags because more suitable tags were found.\n”); av_dict_free(&s->internal->id3v2_meta); if (s->error_recognition & AV_EF_EXPLODE) return AVERROR_INVALIDDATA; } if (id3v2_extra_meta) { if (!strcmp(s->iformat->name, “mp3”) || !strcmp(s->iformat->name, “aac”) || !strcmp(s->iformat->name, “tta”)) { if ((ret = ff_id3v2_parse_apic(s, &id3v2_extra_meta)) < 0) goto fail; if ((ret = ff_id3v2_parse_chapters(s, &id3v2_extra_meta)) < 0) goto fail; } else av_log(s, AV_LOG_DEBUG, “demuxer does not support additional id3 data, skipping\n”); } ff_id3v2_free_extra_meta(&id3v2_extra_meta); if ((ret = avformat_queue_attached_pictures(s)) < 0) goto fail; if (!(s->flags&AVFMT_FLAG_PRIV_OPT) && s->pb && !s->internal->data_offset) s->internal->data_offset = avio_tell(s->pb); s->internal->raw_packet_buffer_remaining_size = RAW_PACKET_BUFFER_SIZE; update_stream_avctx(s); for (i = 0; i < s->nb_streams; i++) s->streams[i]->internal->orig_codec_id = s->streams[i]->codecpar->codec_id; if (options) { av_dict_free(options); *options = tmp; } *ps = s; return 0;fail: ff_id3v2_free_extra_meta(&id3v2_extra_meta); av_dict_free(&tmp); if (s->pb && !(s->flags & AVFMT_FLAG_CUSTOM_IO)) avio_closep(&s->pb); avformat_free_context(s); *ps = NULL; return ret;} ...

April 12, 2019 · 3 min · jiezi

21天学习C语言-第二天

第二天,c程序的组成部分(The Components of a C Program),感觉明显比第一天有深度。示例程序昨天提到,需要用两个命令才能得到可执行程序,其实用一个就能搞定,比如今天的代码,一个命令搞定了。gcc -o multiply multiply.c看下运行效果代码和结果能匹配,做简单的乘法计算。程序的组成部分下面来看这段程序由哪些组成部分包含命令 #include #include的作用是让编译器在编译的时候把要包含的内容添加到当前的程序内。被包含的文件(<>中)通常被叫做头文件,头文件的扩展名必须是.h。示例代码中,我们引入了stdio.h文件,此文件中包含了我们要用的函数,比如printf和scanf虽然我们自己的代码没有相关的声明,但是依然可以用。变量声明int val1, val2, val3;声明变量作用就是申请一块内存空间用来存数据。在C语言中,变量在使用前一定要声明,声明变量告诉编译器这个变量的名字和保存数据的类型,换句话就是声明变量要指明类型,示例代码中我们声明了三个int行的变量。函数原型int product(int x, int y);函数原型(不太确定是不是翻译成原型)的作用是告诉编译器这个函数的名称和参数,同样函数原型也必须在函数使用之前出现,和声明相似。不同的是,声明一个函数需要把函数实现部分也写出来。示例代码中,如果不想写这个函数原型,可以直接把下边的函数声明整体挪到原型位置,依然可以运行,差别暂时还不了解。主函数main() 主函数是程序入口,正常情况下,程序从主函数的第一句开始执行,到最后一句结束。参数位置的void可以不写,大多数编译器都能过,不过书中推荐还是写,用来告诉自己这啥也没传…完全搞不懂在说什么…函数声明int product(int x, int y){…}之前已经说过,函数声明需要有函数实现部分,大括号中的代码就是函数的实现部分,简单一句,返回两个数的相乘结果。注释 注释在任何语言中都有着不可获取的作用。具体写法大家都知道,不做赘言了。应该注意的就是别把没用的内容写进注释,不仅不能增加可读性,还可能误导团队。第二天愉快的完成了!

April 12, 2019 · 1 min · jiezi

编译程序-GCC

一、基本概念二、编译流程1. 预处理阶段功能:插入源文件包含的头文件代码和替换源文件中的宏定义代码命令gcc -E hello.c -o hello.i源文件预处理代码2. 编译阶段功能:将预处理代码转换为汇编代码命令gcc -S hello.i -o hello.s汇编文件3. 汇编阶段功能:将汇编代码转换为机器代码命令gcc -c hello.s -o hello.o目标文件4. 链接阶段功能:将各个目标件链接为可执行程序命令gcc hello.o -o hello可执行文件三、编译选项

April 12, 2019 · 1 min · jiezi

FFmpeg命令集

视频转换成yuv# -ss : 开始时间# -t : 持续时间ffmpeg -i intput.mp4 -ss 00:00:00 -t 00:00:10 output.yuv播放yuvffplay -f rawvideo -video_size 700x1240 output.yuv

April 12, 2019 · 1 min · jiezi

21天学习C语言-第一天

验证下21天能否学会C语言,走起。教材21天学C语言,网上下的英文版,顺便学习下英文,需要的请下载,密码:yhre。老外写书都是比较讲究,前奏特别长,怎么读这本书,各个标注都是什么意思,哪里能找到源码之类的,基本上前20页可以不用看的。如果时间充足,建议读一下。以下挑重点。为什么用C书上介绍了5点,灵活又强大,在程序员中很流行,可移植性好,关键字少,模块化。理解的不深入,总结起来就是好,下面正式开始吧。开发环境及工具最基本的,有台电脑,系统无所谓,可以在网上找到任意系统的配置方法。因为我做前端比较多,所以ide就用vscode了。在网上简单的查了一下,安装了两个插件,完美运行了。更多编译器及IDE用什么写,用什么编译都是外在的,写代码的能力才是自己的!所以,选一款自己喜欢的,开始敲代码!开发步骤用IDE写代码编译链接(这个不是很理解)执行写代码第一天的内容马上要完成了,写个经典的hello world就大功告成了。新建文件,命名为hello.c按照上面的步骤先编译下,文件目录下启动控制台,执行命令 gcc -c hello.c,代码没问题会生成.o文件。第三步,link,执行命令gcc hello.o -o hello,生成可执行文件。最后一步运行,执行命令./hello能看到输出,和预期结果一样,完美运行。

April 11, 2019 · 1 min · jiezi

OpenResty下使用Apache Ant Path匹配库

OpenResty下使用Apache Ant Path匹配库一、简介 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,而lua相对于编译型语言性能比较差,所以我们使用编写sharedobject库的方式集成到OpenResty项目中去。luajit使用ffi调用libcgoantpath.so来实现pattern匹配。 基于以上思路我们实现了一个符合Apache Ant Path标准的动态共享库,Git地址:go-antpath v1.1,为了方大家使用我们还封装了lua版本的lua-antpath v1.0.1,欢迎大家多多指导,共同进步。二、参考http://ant.apache.org/manual/api/org/apache/tools/ant/https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/PathMatcher.htmlgo-antpathlua-antpathhttps://github.com/golang/go/wiki/cgohttps://golang.org/cmd/cgo/https://groups.google.com/forum/#!topic/golang-nuts/Nb-nfVdAyF0三、编译及运行环境3.1 编译环境GNU Make 4.1 golang 1.9.2+3.2 运行环境luajit 2.1 antpath.go (执行make的时候自动下载)lua2go v1.0 (执行make的时候自动下载) cjson (OpenResty自带优良库)四、使用

April 10, 2019 · 1 min · jiezi

Brett Lantz在R中使用C5.0算法实现决策树

Brett Lantz在R中使用C5.0算法实现决策树来源 | 愿码(ChainDesk.CN)内容编辑愿码Slogan | 连接每个程序员的故事网站 | http://chaindesk.cn愿码愿景 | 打造全学科IT系统免费课程,助力小白用户、初级工程师0成本免费系统学习、低成本进阶,帮助BAT一线资深工程师成长并利用自身优势创造睡后收入。官方公众号 | 愿码 | 愿码服务号 | 区块链部落免费加入愿码全思维工程师社群 | 任一公众号回复“愿码”两个字获取入群二维码本文阅读时长:9min决策树学习者是强大的分类器,它利用树结构来模拟特征和潜在结果之间的关系。这种结构之所以得名,是因为它反映了文字树在宽阔的树干上开始的方式,并且当它向上延伸时分裂成更窄的树枝。以同样的方式,决策树分类器使用分支决策的结构,其将示例引导到最终预测的类值。决策树有很多实现,但最著名的是C5.0算法。该算法由计算机科学家J. Ross Quinlan开发,作为其先前算法C4.5的改进版本(C4.5本身是对其迭代二分法3(ID3)算法的改进)。虽然Quinlan向商业客户销售C5.0,但该算法的单线程版本的源代码已公开,因此已被纳入诸如R等程序中。C5.0决策树算法C5.0算法已经成为生成决策树的行业标准,因为它可以直接开箱即用地解决大多数类型的问题。与其他高级机器学习模型相比 ,C5.0构建的决策树通常表现得差不多但更容易理解和部署。此外,如下表所示,算法的弱点相对较小,可以在很大程度上避免。优势一种通用分类器,可以很好地解决许多类型的问题。高度自动化的学习过程,可以处理数字或名义特征,以及缺少数据。不包括不重要的功能。可以在小型和大型数据集上使用。在没有数学背景的情况下可以解释的模型中的结果(对于相对较小的树)。比其他复杂模型更有效。弱点决策树模型通常偏向于具有大量级别的特征的分裂。很容易过度装配或不适合模型。由于依赖于轴并行分割,可能无法对某些关系建模。训练数据的微小变化可能导致决策逻辑发生很大变化。大树很难解释,他们做出的决定似乎违反直觉。为了简单起见,我们早期的决策树示例忽略了机器如何采用分而治之策略所涉及的数学问题。让我们更详细地探讨这一点,以研究这种启发式方法在实践中如何运作。选择最佳分割决策树将面临的第一个挑战是确定要拆分的特征。在前面的示例中,我们寻找一种分割数据的方法,使得生成的分区包含主要包含单个类的示例。示例子集仅包含单个类的程度称为纯度,而仅由单个类组成的任何子集称为纯。可以使用各种纯度测量来识别最佳决策树分裂候选者。C5.0使用熵,这是一种借鉴信息理论的概念,用于量化一组类值中的随机性或无序性。具有高熵的集合非常多样化,并且提供关于可能也属于集合的其他项目的很少信息,因为没有明显的共性。决策树希望找到减少熵的分裂,最终增加组内的同质性。通常,熵以比特来度量。如果只有两个可能的类,则熵值的范围可以从0到1.对于n个类,熵范围从0到log 2 (n)。在每种情况下,最小值表示样本是完全同质的,而最大值表示数据尽可能多样化,并且没有组具有甚至小的多个。在数学概念中,熵被指定为:在该公式中,对于给定的数据段(S),术语c指的是类级别的数量,并且p i 指的是落入级别级别i的值的比例。例如,假设我们有两个类的数据分区:红色(60%)和白色(40%)。我们可以将熵计算为:> -0.60 * log2(0.60) - 0.40 * log2(0.40)[1] 0.9709506我们可以想象所有可能的两级安排的熵。如果我们知道一个类中的例子的比例是x,那么另一个类中的比例是(1-x)。使用curve()函数,我们可以绘制x的所有可能值的熵:> curve(-x * log2(x) - (1 - x) * log2(1 - x), col = “red”, xlab = “x”, ylab = “Entropy”, lwd = 4)如下图:如在x = 0.50处的熵峰值所示,50-50分裂导致最大熵。随着一个类越来越主导另一个类,熵减少到零。为了使用熵来确定要分割的最佳特征,该算法计算由于对每个可能特征的分割而导致的同质性变化,该度量被称为信息增益。特征F的信息增益被计算为分割(S 1)之前的片段中的熵与分割(S 2)产生的分区之间的差异:一个复杂的问题是,在拆分之后,数据被分成多个分区。因此,计算熵(S 2 )的函数需要考虑所有分区的总熵。它通过根据落入该分区的所有记录的比例对每个分区的熵进行加权来实现此目的。这可以在公式中说明如下:简单来说,由分割产生的总熵是n个分区中的每一个的熵的总和,其由落入分区(w i)中的示例的比例加权。信息增益越高,在对该特征进行拆分后创建同类组的功能就越好。如果信息增益为零,则不会减少用于分割此特征的熵。另一方面,最大信息增益等于分割之前的熵。这意味着拆分后的熵为零,这意味着拆分产生完全同质的组。以前的公式假设名义特征,但决策树也使用信息增益来分割数字特征。为此,通常的做法是测试将值划分为大于或小于阈值的组的各种拆分。这会将数字特征缩减为两级分类功能,以便像往常一样计算信息增益。为分割选择产生最大信息增益的数字切割点。注意:尽管它被C5.0使用,但信息增益并不是可用于构建决策树的唯一分裂标准。其他常用标准是基尼指数,卡方统计量和增益比。修剪决策树如前所述,决策树可以继续无限增长,选择拆分功能并划分为更小和更小的分区,直到每个示例完全分类或算法耗尽要分离的功能。但是,如果树长得过大,那么它做出的许多决定将过于具体,模型将过度拟合到训练数据中。修剪决策树的过程涉及减小其大小,以便更好地概括为看不见的数据。该问题的一个解决方案是在树达到一定数量的决策时或当决策节点仅包含少量示例时阻止树增长。这称为提前停止或预先确定决策树。由于树避免做不必要的工作,这是一个吸引人的策略。然而,这种方法的一个缺点是,无法知道树是否会错过细微但重要的模式,如果它变得更大,它将会学到它。另一种称为后修剪的方法涉及种植有意过大的树并修剪叶节点以将树的大小减小到更合适的水平。这通常是比预执行更有效的方法,因为在不首先增加决策树的情况下确定决策树的最佳深度是相当困难的。稍后修剪树允许算法确定发现了所有重要的数据结构。C5.0算法的一个好处是,它是关于修剪的看法 - 它使用相当合理的默认值自动处理许多决策。它的总体策略是对树进行后修剪。它首先会生成一个超级训练数据的大树。之后,删除对分类错误影响很小的节点和分支。在某些情况下,整个分支在树上向上移动或由更简单的决策取代。这些移植分支的过程分别称为子树提升和子树替换。在过度拟合和欠拟合之间取得适当的平衡是一件艺术,但如果模型准确性至关重要,那么可能需要花一些时间使用各种修剪选项来查看它是否能提高测试数据集的性能。总而言之,决策树由于其高准确性和用简单语言制定统计模型的能力而被广泛使用。在这里,我们研究了一种非常流行且易于配置的决策树算法C5.0。与其他决策树实现相比,C5.0算法的主要优势在于可以非常轻松地调整训练选项。

April 8, 2019 · 1 min · jiezi

Unity C# Mathf.Abs()取绝对值性能测试

之前有人提到过取绝对值时 直接写三目运算符比用Mathf.Abs()效率高 没觉得能高太多 今天测了一下 真是不测不知道 一测吓一跳 直接写三目运算符比Mathf.Abs()效率高2-3倍 这性能差距有点不太合理啊! 看下源码发现 很多Mathf的方法就是多封装了一层Math里的方法 把double型转成float型了 即便很简单得方法也没有重新实现 官方有点偷懒了 所以性能差距才会这么大 以后要求性能高的地方要注意 老老实实写一遍 能提升不少性能 ABS效率对比 测试代码: using UnityEngine;using UnityEditor;using System.Diagnostics;/// <summary>/// 执行时间测试/// ZhangYu 2019-04-04/// </summary>public class TimeTest : MonoBehaviour { public int executeTimes = 1; private static Stopwatch watch; private void OnValidate() { times = executeTimes; } private static int times = 1; [MenuItem("CONTEXT/TimeTest/执行")] private static void Execute() { watch = new Stopwatch(); // 数据 float a = 1; // Mathf.Abs watch.Reset(); watch.Start(); for (int i = 0; i < times; i++) { a = Mathf.Abs(a); } watch.Stop(); string msgMathfAbs = string.Format("Mathf.Abs: {0}s", watch.Elapsed); // 自己实现Abs watch.Reset(); watch.Start(); for (int i = 0; i < times; i++) { a = MyAbs(a); } watch.Stop(); string msgMyAbs = string.Format("自定义Abs: {0}s", watch.Elapsed); // 三目运算符Abs watch.Reset(); watch.Start(); for (int i = 0; i < times; i++) { a = a < 0 ? -a : a; } watch.Stop(); string msg3Abs = string.Format("三目运算符Abs: {0}s", watch.Elapsed); print(msgMathfAbs); print(msgMyAbs); print(msg3Abs); } // == 执行次数:10000000 // Mathf.Abs // (1)0.2803558s // (2)0.2837749s // (3)0.2831089s // (4)0.2829929s // (5)0.2839846s // 自定义Abs // (1)0.2162217s // (2)0.2103635s // (3)0.2103390s // (4)0.2092863s // (5)0.2097648s private static float MyAbs(float a) { return a < 0 ? -a : a; } // 三目运算符Abs // (1)0.0893028s // (2)0.1000181s // (3)0.1017959s // (4)0.1001749s // (5)0.1005737s} Mathf.Abs()源码: ...

April 4, 2019 · 2 min · jiezi

CAD快速看图软件怎么在移动端转换导出图纸格式的文件?

CAD快速看图软件怎么在移动端转换导出图纸格式的文件?我们一般再设计CAD图纸文件的时候,通常都是保存为dwg或者是dxf格式的图纸文件,那么为了简单操作方便的话,我们一般都会将图纸进行转换格式操作,那用的最多的就是CAD转图片格式了,那么在电脑上我们都会进行操作转换,要是没有电脑的话,我们在移动端又该怎么进行转换CAD图纸输出图片格式?其实,方法也很简单,那么下面,小编就给大家具体的演示操作一下相关的流程了。步骤一:首先在你的手机界面上快速找到你的CAD相关转换app软件,要是没有的话,我们可以点击任意浏览器或者是手机上自带的应用商店也是可以直接进行安装下载的。步骤二:等到软件安装下载好之后,我们直接打开CAD转换器app即可,然后在操作界面中,我们点击选择CAD转图片格式即可。步骤三:我们一般接收的CAD图纸格式,在你下载好之后,都会显示一个文件存储路径,我们直接点击所以文件,然后在指定的文件夹中上传我们需要进行编辑转换的CAD图纸即可。步骤四:最后找到图纸之后,直接点击开始转换即可,然后稍等一会,图纸转换格式就转换完成了,最后我们将图纸在输出保存即可。那么以上就是转换的全部操作演示过程了,希望可以对大家有所帮助,要是大家还有疑问的话,可以点击迅捷官网查询更多教程哦。

April 2, 2019 · 1 min · jiezi

笔记本电脑录屏的方法

如今自媒体短视频的发展越来越快,所以短视频成为现在的展现形式,所以笔记本电脑怎么录视频?下面我们就一起来看看笔记本电脑录屏的方法吧!使用工具:电脑操作方法:1、我们经常可以在网上看到一些短视频、教程类视频等,但是我们自己也要尝试录制视频,怎么录制电脑视频呢?感兴趣的一起看看下面的内容吧!2、我们需要打开它,设置我们录屏时候需要用到的一些参数,主要是围绕着视频选项、画质设置、音频选项、录制格式还有模式选择这五项参数进行设置与选择。3、然后便是设置我们储存视频所用的文件夹目录,设置文件夹目录我们需要点击更改目录,之后选中我们要替换的文件夹,点击确定,即可更换和设置成功。4、然后我们便可以开始录屏了,点击开始录制即可开始进行录屏,如果想结束录屏我们可以点击旁边的停止录屏来结束录屏。5、假如你平时使用键盘比较顺手的话可以尝试下使用组合键来操控录制,alt+f1是开始和暂停录制,alt+f2是停止录制。6、在录屏结束之后,我们可以点击打开文件夹这个选项,在稍后弹出的文件夹中可以找到我们刚才录制好的视频文件。以上便是笔记本电脑录屏的方法的全部内容了,你们学会了吗?还是比较简单的不是吗?感谢大家阅读小编文章,希望本篇文章对大家能够有所帮助。

April 2, 2019 · 1 min · jiezi

2019“嘉韦思杯”上海市高校网络安全邀请赛write up

ReverseAuthint __cdecl main(int argc, const char **argv, const char **envp){ const CHAR *v3; // ebx HMODULE v4; // eax void (__stdcall *v5)(HMODULE, LPCSTR); // eax char v7; // [esp+1h] [ebp-157h] char v8[4]; // [esp+15h] [ebp-143h] int v9; // [esp+20h] [ebp-138h] int v10; // [esp+26h] [ebp-132h] int v11; // [esp+2Ah] [ebp-12Eh] int v12; // [esp+2Eh] [ebp-12Ah] int v13; // [esp+32h] [ebp-126h] int v14; // [esp+36h] [ebp-122h] int v15; // [esp+3Ah] [ebp-11Eh] __int16 v16; // [esp+3Eh] [ebp-11Ah] int v17; // [esp+40h] [ebp-118h] int v18; // [esp+44h] [ebp-114h] int v19; // [esp+48h] [ebp-110h] int v20; // [esp+4Ch] [ebp-10Ch] int v21; // [esp+50h] [ebp-108h] int v22; // [esp+54h] [ebp-104h] int v23; // [esp+58h] [ebp-100h] int v24; // [esp+5Ch] [ebp-FCh] int v25; // [esp+60h] [ebp-F8h] int v26; // [esp+64h] [ebp-F4h] int v27; // [esp+68h] [ebp-F0h] int v28; // [esp+6Ch] [ebp-ECh] int v29; // [esp+70h] [ebp-E8h] char v30; // [esp+74h] [ebp-E4h] int a_2; // [esp+75h] [ebp-E3h] int v32; // [esp+79h] [ebp-DFh] int v33; // [esp+7Dh] [ebp-DBh] int v34; // [esp+81h] [ebp-D7h] int v35; // [esp+85h] [ebp-D3h] int v36; // [esp+89h] [ebp-CFh] int v37; // [esp+8Dh] [ebp-CBh] int v38; // [esp+91h] [ebp-C7h] __int16 v39; // [esp+95h] [ebp-C3h] int a_1; // [esp+97h] [ebp-C1h] int v41; // [esp+9Bh] [ebp-BDh] int v42; // [esp+9Fh] [ebp-B9h] int v43; // [esp+A3h] [ebp-B5h] int v44; // [esp+A7h] [ebp-B1h] int v45; // [esp+ABh] [ebp-ADh] int v46; // [esp+AFh] [ebp-A9h] int v47; // [esp+B3h] [ebp-A5h] char v48; // [esp+B7h] [ebp-A1h] int v49; // [esp+B8h] [ebp-A0h] int v50; // [esp+BEh] [ebp-9Ah] int v51; // [esp+C2h] [ebp-96h] int v52; // [esp+C6h] [ebp-92h] int v53; // [esp+CAh] [ebp-8Eh] int v54; // [esp+CEh] [ebp-8Ah] int v55; // [esp+D2h] [ebp-86h] int v56; // [esp+D6h] [ebp-82h] int v57; // [esp+DAh] [ebp-7Eh] char v58; // [esp+DEh] [ebp-7Ah] int v59; // [esp+DFh] [ebp-79h] int v60; // [esp+E3h] [ebp-75h] int v61; // [esp+E7h] [ebp-71h] int v62; // [esp+EBh] [ebp-6Dh] int v63; // [esp+EFh] [ebp-69h] int v64; // [esp+F3h] [ebp-65h] int v65; // [esp+F7h] [ebp-61h] int v66; // [esp+FBh] [ebp-5Dh] int16 v67; // [esp+FFh] [ebp-59h] int v68; // [esp+101h] [ebp-57h] int v69; // [esp+105h] [ebp-53h] char v70; // [esp+109h] [ebp-4Fh] int v71; // [esp+10Ah] [ebp-4Eh] int v72; // [esp+10Eh] [ebp-4Ah] int v73; // [esp+112h] [ebp-46h] int v74; // [esp+116h] [ebp-42h] int v75; // [esp+11Ah] [ebp-3Eh] int v76; // [esp+11Eh] [ebp-3Ah] int v77; // [esp+122h] [ebp-36h] int v78; // [esp+126h] [ebp-32h] int v79; // [esp+12Ah] [ebp-2Eh] int v80; // [esp+12Eh] [ebp-2Ah] int v81; // [esp+132h] [ebp-26h] int v82; // [esp+136h] [ebp-22h] int v83; // [esp+13Ah] [ebp-1Eh] int v84; // [esp+13Eh] [ebp-1Ah] int v85; // [esp+142h] [ebp-16h] int v86; // [esp+146h] [ebp-12h] int v87; // [esp+14Ah] [ebp-Eh] int16 v88; // [esp+14Eh] [ebp-Ah] int *v89; // [esp+150h] [ebp-8h] v89 = &argc; sub_402940(); puts( " . \n" " | ROBOTIC AUTHENTICATION SYSTEM\n" " /\/\ (. .) /\n" " ||' |#| \n" " ||__.-\"-\"-.___ \n" " —| . . |–.\ \n" " | : : | ,||,\n" " `..-..’ \/\/\n" " || || \n" " || || \n" " ||| \n"); v49 = 0x539; v50 = 0x60646D51; v51 = 0x64216472; v52 = 0x7364756F; v53 = 0x64697521; v54 = 0x73686721; v55 = 0x51217572; v56 = 0x76727260; v57 = 0x3B65736E; v58 = 1; a_1 = 0x60646D51; v41 = 0x64216472; v42 = 0x7364756F; v43 = 0x64697521; v44 = 0x73686721; v45 = 0x51217572; v46 = 0x76727260; v47 = 0x3B65736E; v48 = 1; v59 = 0x60646D51; v60 = 0x64216472; v61 = 0x7364756F; v62 = 0x64697521; v63 = 0x62647221; v64 = 0x21656F6E; v65 = 0x72726051; v66 = 0x65736E76; v67 = 315; v31 = 0x60646D51; v32 = 0x64216472; v33 = 0x7364756F; v34 = 0x64697521; v35 = 0x62647221; v36 = 0x21656F6E; v37 = 0x72726051; v38 = 0x65736E76; v39 = 315; v68 = 0x6F6F3074; v69 = 0x666D3367; v70 = 3; v28 = 0x6F6F3074; v29 = 0x666D3367; v30 = 3; v71 = 0x6F73646A; v72 = 0x33326D64; v73 = 0x6D6D652F; v74 = 0x13F0101; v24 = 0x6F73646A; v25 = 0x33326D64; v26 = 0x6D6D652F; v27 = 0x13F0101; v75 = 0x57656540; v76 = 0x6E756264; v77 = 0x44656473; v78 = 0x71646279; v79 = 0x6F6E6875; v80 = 0x656F6049; v81 = 0x173646D; v17 = 0x57656540; v18 = 0x6E756264; v19 = 0x44656473; v20 = 0x71646279; v21 = 0x6F6E6875; v22 = 0x656F6049; v23 = 0x173646D; v82 = 0x21746E58; v83 = 0x2F6F6876; v84 = 0x6F6E4221; v85 = 0x75607366; v86 = 0x75606D74; v87 = 0x726F6E68; v88 = 0x120; v10 = 0x21746E58; v11 = 0x2F6F6876; v12 = 0x6F6E4221; v13 = 0x75607366; v14 = 0x75606D74; v15 = 0x726F6E68; v16 = 0x120; v9 = 0x539; strcpy(v8, “r0b0RUlez!”); dword_40AD94 = (int)&v9; dword_40ADA0 = (int)&v49; dword_40AD8C = (char *)&a_1; dword_40AD90 = (char *)&a_2; dword_40AD98 = (int)&v28; lpProcName = (LPCSTR)&v17; lpModuleName = (LPCSTR)&v24; dword_40ADA4 = (char *)&v10; sub_401500(0); v3 = lpProcName; v4 = GetModuleHandleA(lpModuleName); v5 = (void (__stdcall *)(HMODULE, LPCSTR))GetProcAddress(v4, v3); v5((HMODULE)1, (LPCSTR)sub_40157F); puts(dword_40AD8C); scanf("%20s", &v7); if ( !strcmp(&v7, v8) ) { puts(“You passed level1!”); sub_4015EA(0); } return 0;}进入sub_401500函数int __cdecl sub_401500(signed int a1){ int result; // eax _BYTE *i; // [esp+1Ch] [ebp-Ch] if ( a1 <= 9 ) return sub_401500(a1 + 1); for ( i = (_BYTE *)dword_40AD94; ; ++i ) { result = dword_40ADA0; if ( (unsigned int)i >= dword_40ADA0 ) break; *i ^= 1u; } return result;}发现是将main函数中的数据与1进行异或这个先放后面输入v7 r0b0RUlez! 进入到了sub_4015EA函数int __cdecl sub_4015EA(signed int a1){ if ( a1 <= 9 ) return sub_4015EA(a1 + 1); puts(dword_40AD90); dword_40ADA8 = 0x401619; __debugbreak(); return 0;}看一下这个函数的汇编代码.text:004015EA arg_0 = dword ptr 8.text:004015EA.text:004015EA push ebp.text:004015EB mov ebp, esp.text:004015ED sub esp, 18h.text:004015F0 cmp [ebp+arg_0], 9.text:004015F4 jg short loc_401607.text:004015F6 add [ebp+arg_0], 1.text:004015FA mov eax, [ebp+arg_0].text:004015FD mov [esp], eax.text:00401600 call sub_4015EA.text:00401605 jmp short locret_401625.text:00401607 ; —————————————————————————.text:00401607.text:00401607 loc_401607: ; CODE XREF: sub_4015EA+A↑j.text:00401607 mov eax, ds:dword_40AD90.text:0040160C mov [esp], eax ; char *.text:0040160F call puts.text:00401614 call $+5.text:00401619 pop eax.text:0040161A mov ds:dword_40ADA8, eax.text:0040161F int 3 ; Trap to Debugger.text:00401620 mov eax, 0.text:00401625.text:00401625 locret_401625: ; CODE XREF: sub_4015EA+1B↑j.text:00401625 leave.text:00401626 retn.text:00401626 sub_4015EA endp经过动态调试发现还会跳转到sub_40157F函数 解题脚本:data = “74306F6F67336D66"string =““for x in range(0,len(data),2): string += chr(eval(“0x”+data[x:x+2])^1^2)print string所以最后的flag为:r0b0RUlez!w3lld0neobfuse ...

April 2, 2019 · 5 min · jiezi

《C语言学习笔记》数据的输入和输出

数据的输入和输出printf函数printf函数称为格式输出函数,其关键字最末一个字母f即为"格式"(format)之意。其功能是按用户指定的格式,把指定的数据显示到显示器屏幕上。printf函数调用的一般格式。printf函数是一个标准库函数,它的函数原型在头文件"stdio.h"中。printf函数调用的一般形式为:printf(“格式控制字符串”, 输出表列)。其中格式控制字符串用于指定输出格式。格式控制串可由格式字符串和非格式字符串两种组成。格式字符串是以%开头的字符串,在%后面跟有各种格式字符,以说明输出数据的类型、形式、长度、小数位数等。#include <stdio.h>int main(void){ int a=88,b=89; printf("%d %d\n",a,b); printf("%d,%d\n",a,b); printf("%c,%c\n",a,b); printf(“a=%d,b=%d”,a,b); return 0; }/** 通过上面的代码可以看出printf的格式,由于格式控制串不同,输出的结果也不相同。* 第1行的输出语句格式控制串中,两格式串%d之间加了一个非格式字符(空格),因此输出的a、b值之间有一个空格。* 第2行的printf语句格式控制串中加入的是非格式字符(逗号),因此输出的a、b值之间加了一个逗号。* 第3行的格式串要求按字符型输出a、b值。* 第4行中为了提示输出结果又增加了非格式字符串。/printf函数格式中的格式字符串。格式字符串的一般形式为: [标志][输出最小宽度][.精度][长度]类型。最小宽度:用十进制整数来表示输出的最少位数。/当转换值的字符数(含前缀)小于最小宽度说明时,则使用填充符(空格)将数值填充到最小宽度.当转换值的字符数(含前缀)大于最小宽度说明时,最小宽度说明失效。/#include<stdio.h> int main(void){ int x=45,y=-45678; printf("%9d,%3d",x,y);// return 0;}精度:精度格式符以".“开头,后跟十进制整数。本项的意义是:1. 如果输出数字,则表示小数的位数;例如.4表示保留4位有效数字2. 如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。长度:长度格式符为h、l两种,h表示按短整型量输出,l表示按长整型量输出。#include<stdio.h> int main(void){ int x=34,y=-34; printf(“x=%8d,y=%8d\n”,x,y);//表明最小宽度为8,用空格进行填充 printf(“x=%08d,y=%08d\n”,x,y);//用0而不是用空格进行填充 printf(“x=% 08d,y=% 08d\n”,x,y);//空格总是产生一个-号或者是空格 printf(“x=%-8d,y=%-8d\n”,x,y);//-号表示的是左对齐 printf(“x=%- 8d,y=%- 8d\n”,x,y); printf(“x=%-+8d,y=%-+8d\n”,x,y);//+总是产生一个+或-号 printf(“x=%8.4d,y=%8.4d\n”,x,y);//精度.4表示是四位有效数字 printf(“x=%-8.4d,y=%-8.4d\n”,x,y); return 0;}/输出结果:x= 34,y= -34x=00000034,y=-0000034x= 0000034,y=-0000034x=34 ,y=-34x= 34 ,y=-34x=+34 ,y=-34x= 0034,y= -0034x=0034 ,y=-0034/类型格式字符意义d以十进制形式输出带符号整数(正数不输出符号)o以八进制形式输出无符号整数(不输出前缀0)x,X以十六进制形式输出无符号整数(不输出前缀0x)u以十进制形式输出无符号整数f以小数形式输出单、双精度实数e,E以指数形式输出单、双精度实数g,G以%f或%e中较短的输出宽度输出单、双精度实数c输出单个字符s输出字符串#include<stdio.h> int main(void){ int x=31; float f=0.1; char a[10]=“abcde”; printf("%7.3d\n”,x); printf("%7.3x\n",x);//进行无符号十六进制的转换 printf("%7.3o\n",x);//进行无符号八进制的转换 printf("%7.1E\n",f); printf("%7.3f\n",f); printf("%7.3s\n",a); return 0;}/输出结果: 031 01f 0371.0E-001 0.100 abc/综合实例#include <stdio.h>int main(void){ int a=15; long float b=123.1234567; double c=12345678.1234567; char d=‘p’; printf(“a=%d\n”, a); printf(“a(%%d)=%d, a(%%5d)=%5d, a(%%o)=%o, a(%%x)=%x\n\n”,a,a,a,a);// %% 可以输出 % printf(“a=%f\n”, b); printf(“b(%%f)=%f, b(%%lf)=%lf, b(%%5.4lf)=%5.4lf, b(%%e)=%e\n\n”,b,b,b,b); printf(“c=%f\n”, c); printf(“c(%%lf)=%lf, c(%%f)=%f, c(%%8.4lf)=%8.4lf\n\n”,c,c,c); printf(“d=%c\n”, d); printf(“d(%%c)=%c, d(%%8c)=%8c\n”,d,d); return 0;}/输出结果:a=15a(%d)=15, a(%5d)= 15, a(%o)=17, a(%x)=fa=123.123457b(%f)=123.123457, b(%lf)=123.123457, b(%5.4lf)=123.1235, b(%e)=1.231235e+002c=12345678.123457c(%lf)=12345678.123457, c(%f)=12345678.123457, c(%8.4lf)=12345678.1235d=pd(%c)=p, d(%8c)= p/scanf函数scanf函数称为格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。scanf函数是一个标准库函数,它的函数原型在头文件"stdio.h"中。scanf函数的一般形式为:scanf(“格式控制字符串”, 地址表列)scanf函数格式控制字符串格式字符串的一般形式为: %[][输入数据宽度][长度]类型类型格式字符意义d输入十进制整数o输入八进制整数x输入十六进制整数u输入无符号十进制整数f或e输入实型数(用小数形式或指数形式)c输入单个字符s输入字符串* 符:用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值。#include<stdio.h>int main(void){ int a,b; scanf("%d %d %d",&a,&b);//当输入为:1 2 3时,把1赋予a,2被跳过,3赋予b。 printf("%d %d",a,b); return 0; }/执行结果:2 4 52 5/宽度#include<stdio.h>int main(void){ int a,b; scanf("%5d",&a);//输入12345678只把12345赋予变量a,其余部分被截去。 printf("%d",a); scanf("%4d%4d",&a,&b);//输入12345678将把1234赋予a,而把5678赋予b。 printf("%d %d",a,b); return 0;}长度:h表示按短整型量输出,l表示按长整型量输出。地址列表&a、&b分别表示变量a和变量b的地址。这个地址就是编译系统在内存中给a、b变量分配的地址。scanf函数在本质上是给变量赋值,但要求写变量的地址,如&a。&是一个取地址运算符,&a是一个表达式,其功能是求变量的地址。注意事项scanf函数中没有精度控制,如:scanf("%5.2f",&a);是非法的。不能企图用此语句输入小数为2位的实数。scanf中要求给出变量地址,如给出变量名则会出错。如 scanf("%d",a);是非法的,应改为scnaf("%d",&a);才是合法的。在输入多个数值数据时,若格式控制串中没有非格式字符作输入,数据之间的间隔则可用空格,TAB或回车作间隔。C编译在碰到空格,TAB,回车或非法数据(如对"%d"输入"12A"时,A即为非法数据)时即认为该数据结束。在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。#include <stdio.h>int main(void){ char a,b; printf(“input character a,b\n”); scanf("%c%c",&a,&b); printf("%c%c\n",a,b); return 0;}/由于scanf函数"%c%c"中没有空格,输入M N,结果输出只有M。而输入改为MN时则可输出MN两字符。/编程实战二进制转换为十进制#include<stdio.h>#include<math.h>int main(void){ int x,a,result=0,i=0; printf(“请输入仅有4位的二进制数:”); scanf("%d",&x); while(x!=0) { a = x%10; result += apow(2.0,i); i++; x/=10; } printf(“对应的十进制数是:%d\n”,result); return 0;}字符转换:大小写转换#include<stdio.h>int main(void){ char a,b,c; printf(“请用户输入三个小写字母,输入时字母之间使用空格隔开:”); scanf("%c %c %c",&a,&b,&c); printf("%c %c %c相对应的ASCII码值为:%d %d %d\n",a,b,c,a,b,c); printf("%c %c %c相对应大写字母为:%c %c %c\n",a,b,c,a-32,b-32,c-32); return 0;}求三个数里面的最大值int max(int x,int y,int z){ if(x>=y && x>=z) return x; else if(y>=x && y>=z) return y; else return z;} ...

April 1, 2019 · 2 min · jiezi

PDF图纸格式怎么进行快速格式转换?有什么方法?

PDF图纸格式怎么进行快速转换?有什么方法?我们一般设计完成的CAD图纸文件都是保存为dwg或者是dxf格式的,那么有的时候为了工作的需要,我们就需要将图纸进行相对应的图纸格式转换操作了,那么现在相关的图纸转换编辑器都有很多,具体的该怎么进行转换操作?都有什么办法来进行快速转换?其实方法也是有很多的,那么下面就小编平时的操作,给大家介绍几种小方法,希望可以对大家有所帮助哦!方法一:PDF在线转换步骤一:先在浏览器里搜索在线cad转换器,找到相应网址进入官网。可以直接在网页上进行各种CAD版本格式的转换。然后在在线cad转换器官网中,我们可以直接点击界面顶端的“PDF转CAD”按钮,也可以选择“PDF转CAD”一栏右侧的“在线转换”按钮。步骤二:在“PDF转CAD”界面中,操作 “点击选择文件”按钮,找到所需要转换的PDF格式文件打开进行添加。步骤三:根据需要可以对“页码选择”“转换格式”进行相关转换设置。最后点击“开始转换”,稍等片刻,该PDF文件就成功转换成CAD图纸了,可以下载到电脑端进行查看。方法二:PDF软件转换步骤一:首先需要进行打开的是你们电脑上面的迅捷CAD转换器软件,电脑上面没有这款软件的可以去到官网迅捷CAD上面进行下载安装!将其下载之后安装到你们的电脑桌面上,然后就可以准备打开进行操作使用了!进入页面之后选择左边转换选项中的PDF转CAD选项!步骤二:完成以上步骤之后打开选择上方的“添加文件”选项在桌面上或是在文件夹里面找到您想进行查看的图纸文件然后进行打开!步骤三:选择上方输出目录中的“浏览”在桌面上选择在一个文件夹将图纸转换好之后保存在文件夹中,接着将其输出格式进行改变最后点击右下角的批量转换即可完成操作!方法三:PDF移动转换步骤一:在我们的手机上找到并打开手机应用市场,搜索“迅捷CAD转换器”,找到对应APP后,点击下载到手机桌面上即可。或者是我们直接点击任意浏览器点击搜索关键词也是可以实现安装下载的。步骤二:等到软件app下载完成后,手指点击软件图标进入转换的主界面,我们可以看到四个主要CAD转换功能,这里我们直接点击选择PDF转CAD功能即可。然后点击【PDF转CAD】功能进入,软件会自动检索手机中的所 有的CAD图纸,比如dwg、dxf等格式,我们找到并选择需要转换的CAD图纸。或者是在全部文件中自己查找。步骤三:选择好需要转换的图纸后,点 击【开始转换】即 可进入转换状态。软件进入转换状态,稍等一下,PDF转CAD就快速转换完成。那么以上几种小方法,希望大家可以有所帮助哦啊

March 27, 2019 · 1 min · jiezi

linux重定向标准输入后,再重新打开标准输入为什么会失效?

首先来看一段代码:#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(){ int fd1,fd2; fd2 = open(“p1.py”,O_RDONLY,0); dup2(fd2,0); char c; while( read(0,&c,1) > 0) printf("%c",c); close(fd2); fd1 = open("/dev/stdin",O_RDONLY); printf("%d\n",fd1); while( read(fd1, &c, 1) > 0) printf("%c",c); return 0;}先讲一下文件描述符是什么。linux进程每打开一个文件都会返回一个文件描述符(整数)。这个描述符实际是打开的文件在该进程的描述符表上的偏移值。比如说p是描述符表,1是描述符,那么p[1]就能够索引到1描述符对应的打开文件。有了这个偏移值(文件描述符)就能够快速的找到并操作文件。(当然实际的情况是这个文件描述符能够索引到打开文件表表项,然后再通过打开文件表表项索引到对应的V-node节点表表项,而这个v-node节点表表项才代表真正的文件。不过只从逻辑上来看不需要理解这个括号里的说明。)解释一下这段程序:1、首先打开了一个叫做p1.py的文件。2、然后用dup2这个函数使得文件描述符0这个位置的指针指向文件描述符fd2这个位置的指针指向的文件。也就是说本来是这样:p[0] = &fiel1,p[fd2] = &file2,现在p[0] = p[fd2] = &file2。而我们都知道文件描述符0这个位置对应的文件file1是标准输入文件/dev/stdin。那么这个函数的意义就是把标准输入重定向到了p1.py这个文件里了。之后再用标准输入比如说scanf(),来读,那么都是从p1.py这个文件里读了。3、然后把这个文件里的东西读出来并输出到屏幕上。4、打开/dev/stdin这个文件,这个文件是标准输入。5、把这个文件里的东西读出来并打印到屏幕上。预期结果是什么?执行到第5步应该停下来,等待键盘输入。然后把输入的东西打印到屏幕。实际结果?没有等待键盘输入,它直接把p1.py的文件内容输出到屏幕上,也就是说和上面的输出是一样的!!!why???how???我打开了一个文件,应该读取文件里的内容。而这个文件是标准输入,那么既然是标准输入(从键盘输入),我还没输入呢,怎么就输出结果了呢???而且结果还很奇特。。。这是因为,标准输入这个文件/dev/stdin是个链接文件!!!它存放的是别的文件的地址!!!如果文件描述符指向的文件是个普通文件,那么把这个文件描述符指向别的文件,就是真的指向了别的文件。而这里的文件描述符指向的是个链接文件,那么把这个文件描述符指向别的文件,意味着什么???意味着它把这个链接文件里的内容(地址)更改了,而它仍然指向这个文件。只不过它知道这是个链接文件,因此它会访问的是这个链接文件中的地址对应的文件。理解了上面的操作就可以说得通了,dup2对于链接文件只是修改了文件中的地址,它并没有真正指向别的文件,这也导致了一个问题,那就是它把这个链接文件给修改了,如果进程再次打开这个链接文件,那么链接文件之前存的地址就没有了,因此执行fd1 = open("/dev/stdin",O_RDONLY);这个的时候,实际上是又一次打开了p1.py这个文件!!!因为/dev/stdin这个文件里存放的地址已经是p1.py这个文件的地址了。。。

March 25, 2019 · 1 min · jiezi

什么是标准输入,标准输出(stdin,stdout)?

要弄清什么是标准输入输出。首先需要弄懂什么是IO。IO的I是Input的意思,O是output的意思。意味着输入和输出。更确切的含义是I:从外部设备输入到内存O:从内存输出到外部设备而标准输入和标准输出是干什么的?它们是用于IO的。那么它们属于IO的哪个部分?内存?还是外部设备?答案显然是外部设备(逻辑上的外部设备,为什么?接着看)。更具体的含义?在linux操作系统中,外部设备用什么表示?是用文件。linux中一切设备皆是文件!因此标准输入和输出更具体的含义是文件。它们是哪两个文件?它们是/dev/stdin这个文件和/dev/stdout这个文件。也就是说所谓的标准输入和标准输出其实就是两个linux下的文件。linux的文件类型有:1、普通文件2、字符设备文件3、块设备文4、目录文件5、链接文件6、管道文件7、套接字文件思考一下?它们是什么文件?它们在/dev目录下,它们是设备文件吗?那么所谓的从标准输入读是什么意思?逻辑上来看:就是打开/dev/stdin这个文件,然后把这个文件里的内容读进来。输出到标准输出是什么意思?逻辑上来看:就是打开/dev/stdout这个文件,然后把内容输出到这个文件里去。为什么是从逻辑上来看?因为它们不是设备文件!!!所以它们不代表一个设备。linux里一切皆是文件,设备是文件,但是文件不一定是设备!那它们是什么文件?他们是链接文件。(可以用ls -l /dev来查看 l开头的就是链接文件。)什么是链接文件?文件内容是另一个文件的地址的文件称为链接文件。因此,打开、读或者写 /dev/stdin和/dev/stdout 实际上是打开、读或者写这两个文件存放的地址对应的设备文件。明白它们是链接文件不是设备文件有什么用?明白这一点才能知道重定向失效的原理。这个下一篇文章再讲!

March 25, 2019 · 1 min · jiezi

大小端

大小端一般人的书写习惯:数据左边的为高位,右边的为低位,地址则相反:地址左边的为低位,右边的为高位例如数据16位数据:0x12345678,12为数据的高位,78位数据的低位大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。例如四字节的数据:0x12345678,地址低->高,低地址存放高位,高地址存放位,存储为:12345678小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。例如四字节的数据0x12345678,地址:低->高,高地址存放高位,低地址存放低位,存储为:78563412只需记住与我们阅读习惯一致的为大端模式。判断方法union方法union的特殊性会申请内部最大的长度为union的长度,取值会从地址的开始往后读取#include <stdio.h>union check_point{int a;char b;} check_point;int main(){ check_point.a=1; if(check_point.b == 1) printf(“LITTLE ENDIAN!\n”); else printf(“BIG ENDIAN !\n”); return 0;}我的测试机为mac,为小端模式,所以执行结果是LITTLE ENDIAN!linux内核也是用了类似的方法:tatic union { char c[4]; unsigned long mylong; } endian_test = {{ ’l’, ‘?’, ‘?’, ‘b’ } };#define ENDIANNESS ((char)endian_test.mylong)测试用例如下:#include <stdio.h>static union { char c[4]; unsigned long mylong; } endian_test = {{ ’l’, ‘?’, ‘?’, ‘b’ } };#define ENDIANNESS ((char)endian_test.mylong)int main(){ printf("%c \n",ENDIANNESS); return 0;}测试结果:llinux内核可直接使用ENDIANNESS宏的返回结果判断大小端int 1的特殊性int 1的大端存储方式为地址:低->高,高地址存放低位,低地址存放高位00000001小端存储方式为地址:低->高,高地址存放高位,低地址存放低位01000000#include <stdio.h>int main(){ int a = 1; char pc = (char)(&a); if (pc == 1) printf(“LITTLE\n”); else printf(“BIG\n”); return 0;}输出结果为:LITTLE ...

March 19, 2019 · 1 min · jiezi

通过TCP与简易内存数据库进行数据交互的实现

目的运用TCP相关原理,实现一个简单的server端和client端的数据库交互程序,可以将client端输入的指令被server端解析,将返回信息又返送给client端。之前的简单内存数据库的实现:T-Tree、T*-Tree的理解与简单内存数据库的实现TCP/IP,socket等相关计算机网络原理七层网络模型七层网络模型TCP/IPTCP/IP是互联网协议簇的统称。TCP-transmission control protocal-传输控制协议IP-Internet Protocal-因特网协议UDP 是User Datagram Protocol 是无连接类型的传输层协议,socket是什么socket是对TCP/IP协议的封装,socket翻译为套接字,socket是一个接口/插座。TCPIP 是Socket的一种实现,Socket并不只有TCP/IP。两种socket:stream sockets,datagram socketsocket其实不止两种。stream socket:串流式socket。是有连接类型的,网页浏览器所使用的 HTTP 协议是用 stream sockets 取得网页。datagram socket:讯息式socket。是无连接类型的,用于语音通信,视频传输较多。TCP serverserver端socket()函数#include <sys/types.h>#include <sys/socket.h>int socket(int domain, int type, int protocol);domain一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。type指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。protocol指定协议。套接口所用的协议。如不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。bind()函数#include <sys/types.h>#include <sys/socket.h>int bind(int sockfd, struct sockaddr *my_addr, int addrlen);bind()将一本地地址与一socket捆绑.sockfdsockfd 是 socket() 传回的 socket file descriptor。my_addrmy_addr是指向包含你的地址资料丶名称及 IP address 的 struct sockaddr 之指针。addrlenaddrlen 是以 byte 为单位的地址长度。connect#include <sys/types.h>#include <sys/socket.h>int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);connect()从client端连接到server端listen(),accept()int listen(int sockfd, int backlog);—————————————————————–#include <sys/types.h>#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t addrlen);send(),recv()int send(int sockfd, const void msg, int len, int flags);————————————————————int recv(int sockfd, void buf, int len, int flags);send() 会返回实际有送出的 byte 数,可能会少与所要传送的数目。recv()若返回0,则说明远端那边已经关闭了你的连接close()close(sockfd);关闭socket。一个tcp server 的示例代码:#include <netdb.h> #include <netinet/in.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #define MAX 80 #define PORT 8080 #define SA struct sockaddr // Function designed for chat between client and server. void func(int sockfd) { char buff[MAX]; int n; // infinite loop for chat for (;;) { bzero(buff, MAX); // read the message from client and copy it in buffer read(sockfd, buff, sizeof(buff)); // print buffer which contains the client contents printf(“From client: %s\t To client : “, buff); bzero(buff, MAX); n = 0; // copy server message in the buffer while ((buff[n++] = getchar()) != ‘\n’) ; // and send that buffer to client write(sockfd, buff, sizeof(buff)); // if msg contains “Exit” then server exit and chat ended. if (strncmp(“exit”, buff, 4) == 0) { printf(“Server Exit…\n”); break; } } } // Driver function int main() { int sockfd, connfd, len; struct sockaddr_in servaddr, cli; // socket create and verification sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf(“socket creation failed…\n”); exit(0); } else printf(“Socket successfully created..\n”); bzero(&servaddr, sizeof(servaddr)); // assign IP, PORT servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(PORT); // Binding newly created socket to given IP and verification if ((bind(sockfd, (SA)&servaddr, sizeof(servaddr))) != 0) { printf(“socket bind failed…\n”); exit(0); } else printf(“Socket successfully binded..\n”); // Now server is ready to listen and verification if ((listen(sockfd, 5)) != 0) { printf(“Listen failed…\n”); exit(0); } else printf(“Server listening..\n”); len = sizeof(cli); // Accept the data packet from client and verification connfd = accept(sockfd, (SA)&cli, &len); if (connfd < 0) { printf(“server acccept failed…\n”); exit(0); } else printf(“server acccept the client…\n”); // Function for chatting between client and server func(connfd); // After chatting close the socket close(sockfd); } client端一个tcp client的示例代码:// Write CPP code here #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #define MAX 80 #define PORT 8080 #define SA struct sockaddr void func(int sockfd) { char buff[MAX]; int n; for (;;) { bzero(buff, sizeof(buff)); printf(“Enter the string : “); n = 0; while ((buff[n++] = getchar()) != ‘\n’) ; write(sockfd, buff, sizeof(buff)); bzero(buff, sizeof(buff)); read(sockfd, buff, sizeof(buff)); printf(“From Server : %s”, buff); if ((strncmp(buff, “exit”, 4)) == 0) { printf(“Client Exit…\n”); break; } } } int main() { int sockfd, connfd; struct sockaddr_in servaddr, cli; // socket create and varification sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { printf(“socket creation failed…\n”); exit(0); } else printf(“Socket successfully created..\n”); bzero(&servaddr, sizeof(servaddr)); // assign IP, PORT servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr(“127.0.0.1”); servaddr.sin_port = htons(PORT); // connect the client socket to server socket if (connect(sockfd, (SA)&servaddr, sizeof(servaddr)) != 0) { printf(“connection with the server failed…\n”); exit(0); } else printf(“connected to the server..\n”); // function for chat func(sockfd); // close the socket close(sockfd); } 通过TCP与简易内存数据库的数据交互为了让client端和server中的内存数据库,通信如果server端和client建立连接之后则进入一个大循环,大循环的退出条件是client端发去“EXIT”,client端随即断开连接。通过TCP和内存数据库通信的所以代码所有代码都在github里:slarsar/ttree_mmdb_tcp_server_client参考GeeksforGeeksBeej’s Guide to Network Programming 简体中文版 ...

March 18, 2019 · 3 min · jiezi

面试点滴

语言面试题#include <iostream>using namespace std;class A{public: A() : m_ival(0) { test(); } virtual void func() { cout << m_ival << endl; } void test() { func(); }public: int m_ival;};class B : public A{public: B() { test(); } void func() { ++ m_ival; cout << m_ival << endl; }};int main(){ A* p = new B; p->test(); delete p; return 0;}问 1:分析下面的程序,给出运行结果输出:012问 二 :在子类与父类构造函数中都调用test(), 那么编译器是怎么确定应该调用的哪个 func() 函数版本呢?相关概念:构造函数未结束前,虚函数表未初始化完成,因此不能发生多态动态联编不等价于多态:动态联编:编译原理中的概念多 态:面向对象理论。 C++ 编译器在实现面向对象的多态时,使用动态联编的概念发生了什么?在构造 A 部分时:在构造 A 部分时,回去构造 A 部分的虚函数表。在执行 A 的构造函数的函数体时,虚函数表是【完整】的在 A 的构造函数中调用 Func(), 发生动态联编,查找虚函数表得到具体函数地址A 中的 Func 被调用在构造 B 部分时:在构造 B 部分时,【虚函数表被更新】在 B 的构造函数中调用 Func(), 发生动态联编,查找虚函数表得到具体函数地址B 中的 Func 被调用总结:在构造函数中,调用虚函数不会发生多态,但虚函数是动态联编的虚函数的调用,都是通过查找虚函数表完成的问 三:上述的代码是否有其它问题?问题 1, 未判断内存是否申请成功问题 2A* p = new B; delete p;A 类指针 p 指向 B 类对象,在 delete 时,析构函数不是虚函数,因此,编译器就只会根据 p 的类型调用 A 类的析构函数, B 类的析构函数不会被调用!!!要点面试官需要的不只是“正确答案”面试官会想要知道应试者的解题思路面试官会从题中间“随机抓取”细节进行追问面试官会把问题进行扩展(如:从语言扩展到编译器)算法面试题HR 问:编写代码将一个链表反转打印面试者:需要思考的问题这具体是一个什么样的链表(单链表?双向链表?循环链表)链表中节点的数据是否允许改变时间复杂度有没有要求。。。struct Node{ int v; struct Node* next;};void reversse_display_list(struct Node* list){ if( list ) { reversse_display_list(list->next); printf("%d “, list->v); }}void reversse_display_list_stack(struct Node* list){ if( list ) { LinkStack<int> stack; while( list ) // O(n) { stack.push(list->v); list = list->next; } while( stack.size() > 0 ) // O(n) { printf("%d “, stack.top()); stack.pop(); } printf("\n”); }}struct Node* reverse_list(struct Node* list) // O(n){ if( list ) { struct Node* guard = NULL; struct Node* next = list->next; while( next ) { list->next = guard; guard = list; list = next; next = list->next; } list->next = guard; } return list;}要点面试不是笔试,面试考察的不只是技术面试官需要全方位考察应试者(沟通,品质,态度)面试中尽量与面试官交流,获取解题的有用信息面试中尽量写出完整代码,而不是纸上谈兵的忽悠。。。发散性思维问题不用加减乘除运算符做整数加法面试者: 需要思考的问题面试官想考察什么?加法的底层本质是什么?是否可以绕开 “+” 操作符的使用。。。int add_1(int a, int b){ int ret = 0; asm volatile ( “movl %%eax, %%ecx\n” “addl %%ebx, %%ecx\n” : “=c”(ret) : “a”(a), “b”(b) ); return ret;}int add_2(int a, int b){ int part = 0; int carry = 0; do { part = a ^ b; carry = (a & b) << 1; a = part; b = carry; } while( b != 0 ); return a;}要点不同公司对人才的要求是不同的一些公司会更看重应试者的可塑性学习,分析,思考能力是工作中必备的(面试需要考察)计算机系统底层原理的学习必不可少。。。 ...

March 18, 2019 · 2 min · jiezi

【笔试题精选】_5

在一个二维数组中,每一行都按照从左到右的顺序排序,每一列都按照从上到下的顺序排序。请实现一个函数用于判断数组中是否包含指定的数。函数原型: bool find_in_matrix(int matrix[N][M], int value);说明: 查找成功时返回true, 返回失败时返回false.template<typename T, int N, int M>bool find_in_matrix(T matrix[N][M], T value) // O(N + M){ if( (matrix[0][0] <= value) && (value <= matrix[N-1][M-1]) ) { int r = 0; int c = M - 1; while( (r < N) && (c >= 0) ) { if( value == matrix[r][c] ) { return true; } else if( value < matrix[r][c] ) { c–; } else { r++; } } } return false;}写一个函数,打印二叉树中某层的所有节点二叉树节点定义:struct Node{ int v; Node* left; Node* right;};函数原型: void print_node_at_level(Node* node, int level);说明: 将 level 层的节点所保存的值打印在同一行void print_node_at_level(Node* node, int level){ if( node != NULL ) { if( level == 0 ) { cout<<node->v<<" “; } else { print_node_at_level(node->left, level-1); print_node_at_level(node->right, level-1); } }}void print_node_at_level_ex(Node* node, int level){ if( node != NULL ) { list<Node*> nl; list<int> ll; nl.push_back(node); ll.push_back(0); while( nl.size() > 0 ) { Node* n = nl.front(); int cl = ll.front(); nl.pop_front(); ll.pop_front(); if( cl == level ) { cout<<n->v<<” “; } else if( cl < level ) { if( n->left != NULL ) { nl.push_back(n->left); ll.push_back(cl+1); } if( n->right != NULL ) { nl.push_back(n->right); ll.push_back(cl+1); } } } }}编写一个函数用于删除二叉树中的度数为 1 的所有节点要求:节点删除后其唯一的子节点代替它的位置struct Node{ int v; Node* left; Node* right;};void print_div(int p){ for(int i=0; i<p; i++) { cout<<”-"; }}void print_tree(Node* node, int p){ if( node != NULL ) { print_div(p); cout<<node->v<<endl; if( (node->left != NULL) || (node->right != NULL) ) { print_tree(node->left, p+1); print_tree(node->right, p+1); } } else { print_div(p); cout<<endl; }}void print_tree(Node* node){ print_tree(node, 0);}void delete_one_degree_node(Node*& node){ if( node != NULL ) { if( (node->left != NULL) && (node->right != NULL) ) { delete_one_degree_node(node->left); delete_one_degree_node(node->right); } else if( (node->left != NULL) && (node->right == NULL) ) { node = node->left; delete_one_degree_node(node); } else if( (node->left == NULL) && (node->right != NULL) ) { node = node->right; delete_one_degree_node(node); } }}输入一个数组,数组里面可能有正数也有负数。数组中一个或连续的多个元素组成一个子数组。求所有子数组的和的最大值。要求:时间复杂度为 O(n)template<typename T>bool max_sub_array_sum(T array[], int len, T& max_sum){ int ret = (len > 0) && (array != NULL); if( ret ) { T sum = array[0]; T cur = array[0]; for(int i=1; i<len; i++) { cur = cur + array[i]; if( cur < array[i] ) { cur = array[i]; } if( cur > sum ) { sum = cur; } } max_sum = sum; } return ret;}在一个整型数组中只可能有 0,1,2 三种数字重复出现,编写一个函数对这样的数组进行排序。void swap(int& a, int& b){ int c = a; a = b; b = c;}void three_element_sort(int array[], int len){ int* ts = new int[len]; int p = 0; if( ts != NULL ) { for(int i=0; i<3; i++) { for(int j=0; j<len; j++) { if( array[j] == i ) { ts[p++] = i; } } } for(int i=0; i<len; i++) { array[i] = ts[i]; } delete[]ts; }}void three_element_sort_ex(int array[], int len){ int p0 = 0; int p2 = len - 1; while( array[p0] == 0 ) p0++; while( array[p2] == 2 ) p2–; for(int i=p0; i<=p2;) { if( array[i] == 0 ) { swap(array[i], array[p0++]); while( array[p0] == 0 ) p0++; if( i < p0 ) i = p0; } else if( array[i] == 2 ) { swap(array[i], array[p2–]); while( array[p2] == 2 ) p2–; } else { i++; } }}求 1 + 2 + 3 + … + n 的和要求:不能使用 if, while, for, switch, ?: 等条件语句,不能使用 ==, !=, <=, >, <= 等比较运算符,也不能调用外部库函数。只能使用加减法操作操作符,不能使用乘除法操作符class Sum{private: static int N; static int S; Sum();public: static int Calculate(int n);};int Sum::N = 0;int Sum::S = 0;Sum::Sum(){ S = S + N; N = N - 1;}int Sum::Calculate(int n){ int ret = 0; Sum* p = NULL; N = n; S = 0; p = new Sum[N]; ret = S; delete[]p; return ret;}int main(){ cout<<Sum::Calculate(10)<<endl; cout<<Sum::Calculate(100)<<endl; return 0;}typedef int(Func)(int);int end_func(int n);int recusive_func(int n);Func* Array[] = { end_func, // 0 recusive_func // 1};int end_func(int n){ return n;}int recusive_func(int n){ return n + Array!!(n-1);}int sum(int n){ return recusive_func(n);}int main(){ cout<<sum(10)<<endl; cout<<sum(100)<<endl; return 0;}template<int N>class Sum{public: static const int Value = N + Sum<N-1>::Value;};template<>class Sum<0>{public: static const int Value = 0;};int main(){ cout<<Sum<10>::Value<<endl; cout<<Sum<100>::Value<<endl; return 0;} ...

March 17, 2019 · 4 min · jiezi

【笔试题精选】_4

下面代码是否有错误?如果有错,错在哪里?struct Test{ Test() {}; Test(int i){} void func(){}};int main(){ Test t1(1); Test t2(); t1.func(); t2.func();}答案:1. Test t2(); ==> Test t2;warning: empty parentheses interpreted as a function declaration2. t2.func(); ==>error: base of member reference is a function; perhaps you meant to call it with no arguments?3. int main() 缺少返回值C++ 编译器需要兼容 C 编译器,编译过现有的 C 代码,因此没有报错下面的代码输出什么?为什么?class Test{private: int m_i; int m_j;public: Test(int v) : m_j(v), m_i(m_j) { } int getI() { return m_i; } int getJ() { return m_j; }};int main(){ Test t1(1); Test t2(2); cout << t1.getI() << " " << t1.getJ() << endl; cout << t2.getI() << " " << t2.getJ() << endl;} 输出:随机数, 1随机数, 2说明:成员的初始化顺序与成员的声明顺序相同成员的初始化顺序与初始化列表中的位置无关本题中成员变量在未初始化前存储的是随机数参考:初始化列表的使用下面的代码输出什么?为什么?class Test{private: int m_i; int m_j;public: Test() { cout << “Test()” << endl; } Test(int v) { cout << “Test(int v)” << endl; } ~Test() { cout << “~Test()” << endl; }};Test Play(Test t){ return t;}int main(){ Test m_t = Play(5); // Play(Test(5))}输出:cout << “Test(int v)” << endl; // Play(5); ==> Play(Test(5))cout << “~Test()” << endl; // t 析构cout << “~Test()” << endl; // m_t 析构Which virtual fucntion re-declaration of the Derived class are correct ?A. Base* Base::copy(Base*); Base* Derived::copy(Derived*);B. Base* Base::copy(Base*); Derived* Derived::copy(Base*);C. int Base::count(); int Derived::count();D. void Base::func(Base*)const; void Derived::func(Base*);答案:【C】说明:多态发生的条件:发生在派生类与基类之间;函数签名必须完全一致;下面的程序输出什么?为什么?class Base{public: virtual void func() { cout << “Base::func()” << endl; }};class Child:public Base{public: void func() { cout << “Child::func()” << endl; }};int main(){ Base* pb = new Base(); pb->func(); Child* pc = (Child*)pb; // 注意这里!!! pc->func(); delete pc; pb = new Child(); pb->func(); pc = (Child*)pb; pc->func();}输出:cout << “Base::func()” << endl; // 正常调用cout << “Base::func()” << endl;cout << “Child::func()” << endl; // 多态调用cout << “Child::func()” << endl; // 正常调用说明:Child* pc = (Child*)pb; ==> 子类对象指针指向父类对象,这是及其危险的!!可是为什么没有发生程序错误呢?因为有虚函数表的存在,无论是子类对象指针还是父类对象指针指向父类对象,都会发生相同的查找过程当 func 不是虚函数时,就不会发生虚函数表的查找过程,此时一定会发生程序崩溃参考:C++对象模型分析A C++ develoer wants to handle a static_cast<char*>() operation for the String class shown below. Which of the following options are valid declarations that will accomplish task ?class String{public:// …// declaration goes here}A. char* operator char*();B. operator char*();C. char* operator();D. char* operator String();答案:【B】以下两种情况:(1)new 一个 10 个整型变量的数组 (2)分 10 次 new 一个整型变量。 哪个占用的空间更大?A. 1B. 2C. 一样多D. 无法确定答案:【B】说明:下面程序输出什么?int main(){ int v[2][10] = { { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {11, 12, 13, 14, 15, 16, 17, 18, 19, 20} } int (a)[8] = (int()[8])v; cout << a << endl; cout << (a + 1) << endl; cout << (a + 1) << endl; cout << (a[0] + 1) << endl; cout << a[1] << endl;}输出:19229参考:多维数组和多维指针下面的程序输出什么?为什么?class Base{public: int a; Base() { a = 1; } void println() { cout << a << endl; }};class Child : public Base{public: int a; Child() { a = 2; }};int main(){ Child c; c.println(); cout << c.a << endl;}输出:12说明:c.println(); ==> 父类中的 printlin 被调用,由于作用域, 父类中的 a 被访问c.a ==> 发生同名覆盖参考:父子间的冲突用 C/C++ 语言实现一个存储整型数据的栈结构要求实现以下功能:1. 入栈操作 push2. 出栈操作 pop3. 栈大小操作 size4. 栈中最小元素 minclass IntStack{private: list<int> m_stack; list<int> m_cmin;public: void push(int v); int pop(); int top(); int size(); int min();};void IntStack::push(int v){ m_stack.push_front(v); if( m_cmin.size() != 0 ) { if( v < m_cmin.front() ) { m_cmin.push_front(v); } else { m_cmin.push_front(m_cmin.front()); } } else { m_cmin.push_front(v); }}int IntStack::pop(){ int ret = m_stack.front(); m_stack.pop_front(); m_cmin.pop_front(); return ret;}int IntStack::top(){ return m_stack.front();}int IntStack::size(){ return m_stack.size();}int IntStack::min(){ return m_cmin.front();}编程实现二叉树的相等比较,当二叉树每个节点的值对应相等时,二叉树相等,否则不相等struct BTreeNode{ int v; BTreeNode left; BTreeNode right;};函数原型:bool BTreeCompare(BTreeNode b1, BTreeNode b2);深度优先:bool BTreeCompare(BTreeNode b1, BTreeNode b2){ bool ret = false; if( (b1 != NULL) && (b2 != NULL) ) { ret = (b1->v == b2->v) && BTreeCompare(b1->left, b2->left) && BTreeCompare(b1->right, b2->right); } if( (b1 == NULL) && (b2 == NULL) ) { ret = true; } return ret;}广度优先:bool BTreeCompareEx(BTreeNode b1, BTreeNode b2){ bool ret = true; list<BTreeNode*> l1; list<BTreeNode*> l2; l1.push_back(b1); l2.push_back(b2); while( ret && l1.size() && l2.size() ) { BTreeNode* n1 = l1.front(); BTreeNode* n2 = l2.front(); l1.pop_front(); l2.pop_front(); if( (n1 != NULL) && (n2 != NULL) ) { ret = (n1->v == n2->v); l1.push_back(n1->left); l1.push_back(n1->right); l2.push_back(n1->left); l2.push_back(n2->right); } else if( (n1 == NULL) && (n2 != NULL) ) { ret = false; } if( (n1 != NULL) && (n2 == NULL) ) { ret = false; } } return ret && (l1.size() == 0) && (l2.size() == 0);} ...

March 16, 2019 · 4 min · jiezi

【笔试题精选】_3

下面的代码输出什么?为什么?#include <iostream>#include <malloc.h>using namespace std;class A{private: static int c_count;public: A() { c_count++; } ~A() { c_count–; } static void Count() { cout<<c_count<<endl; }};int A::c_count = 0;int main(){ A* a = static_cast<A*>(malloc(sizeof(A))); a->Count(); delete a; a->Count(); return 0;}输出:0-1说明:A* a = static_cast<A*>(malloc(sizeof(A))); // a 仅指向堆空间中A对象大小的内存, malloc 仅负责内存申请,不会触发构造函数的调用a->Count(); // 输出 0delete a; // 析构函数被调用 , 此时 a 成为野指针a->Count(); // 但是这里为什么没有运行崩溃呢? 因为,在C++中调用静态成员时,只使用对象类型,而不使用具体的对象a->Count(); ==> A::Count() 参考:关于 new malloc free delete 的疑问下面的程序输出什么?为什么?class A{public: virtual void test(int i) { cout<<“A::test”<<i<<endl; } void test() { cout<<“A::test”<<endl; }};class B : public A{public: void test(double i) { cout<<“B::test”<<i<<endl; }};int main(){ A* a = new B(); B* b = new B(); a->test(5); b->test(5); return 0;}输出:cout<<“A::test”<<5<<endl; // 注意这里的输出,没有发生多态行为cout<<“B::test”<<5<<endl;说明:多态发生的条件:发生在派生类与基类之间;函数签名必须完全一致;题目中派生类与基类的同名成员函数参数列表不同,仅发生同名隐藏参考:父子间的冲突下面的描述正确的是?A. 面向对象编程需要面向对象语言的支持,如Java 和 C++ 等B. 封装,继承和多态是面向对象的基本特征C. 继承是面向对象中代码复用的唯一方式D. 多态的工作方式与重载相同答案: 【B】说明:A. 面向对象是软件设计思想,与编程语言无关。面向对象语言仅是加入了对面向对象概念的原生支持C. 继承,组合D. 重载,静态联编,编译器确定调用关系,发生在同一作用域中; 多态,动态联编,运行时确定调用关系,发生在有继承关系的类之间下面的代码输出什么?为什么?class A{private: int i;public: virtual void test() { cout<<“A::test”<<endl; }};class B : public A{private: int i;public: void test() { cout<<“B::test”<<endl; }};void f(A* p, int len){ for(int i=0; i<len; i++) { p[i].test(); }}int main(){ B b[3]; f(b, 3); return 0;}输出:cout<<“B::test”<<endl;断错误说明:1 .void test(); 将发生多态行为,第一次打印正常输出2. 在题目的函数调用中,void f(A* p, int len);p 将指向子类对象3. 指针运算时(编译期确定): Type t[N] t[i] <==> (t + i) t + i <==> (unsigned int)t + i * sizeof(Type) 4. 指针 p 指向的类型为 A,指针运算时的步长为 sizeof(A)5. class A 与 class B 为继承关系, 因此 sizeof(B) > Sizeof(A)下面描述正确的是?A. 一个应用程序启动后成为一个进程B. 进程是操作系统资源分配的基本单位C. 一个进程可以创建多个线程,每个线程都共享进程的资源D. 操作系统可以在创建进程的时候不创建任何一个线程答案:【A B C】下面程序输出什么?为什么?class A{private: static int i;public: A() { i++; } A(const A&) { i++; } static void output() { cout<<i<<endl; }};A f(A& a){ A aa = a; return a;}int A::i = 0;int main(){ A a; f(a).output(); return 0;}输出:i = 3 说明:第一次 i++ : A a;第二次 i++ : A aa = a;第三次 i++ : return a; // a 初始化一个临时对象用于返回下面的程序输出什么?为什么?#include <iostream>using namespace std;#define FUNC(a, b) a = a + b; \b = a - b; \a = a - bint main(){ int a = 3; int b = 4; if(a > b) FUNC(a, b); cout<<a<<" “<<b<<endl;}输出:4 -1说明:宏的副作用之一,仅进行简单的文本替换if(a > b) FUNC(a, b); ==>if(a > b) a = a + b; b = a - b; a = a - bTelnet 协议是基于下面哪种协议开发而来的?A. TCPB. UDPC. TCP and UDPD. None of above答案:【A】Please choose the correct option for the ISR below:()interrupt double service(double p){ return pp;}A. ISR function should not return any value, service() cannot be used as a ISRB. ISR function should not accept any parameter, service() cannot be used as a ISRC. Servica() is a valid ISRD. None of above答案:【A B】说明:中断服务程序需要遵循的规则:1. 中断服务程序不能有返回值2. 中断服务程序不能有参数3. 中断服务程序里面不要有浮点运算4. 中断服务程序中不应有 printf 等不可重入的函数有一组整型数,其中除了 2 个数字以外的其它数字都是俩俩成对出现的,编写程序找出这两个不成对出现的数字。函数原型:void search_diff(int array[], int len, int* pa, int* pb);示例:void search_diff(int array[], int len, int* pa, int* pb);int a = 0;int b = 0;int array[] = {3, 4, 5, 5, 3, 4, 1, 6, 6, 7, 2, 8, 7, 8};search_diff(array, sizeof(array)/sizeof(array), &a, &b); //调用后 a,b 为 1,2 或者 a,b 为2,1int first_one_bit(unsigned int v){ int ret = 0; while( (v != 0) && ((v & 1) == 0) ) { v = v >> 1; ret ++; } return ret;}void search_diff(int array[], int len, int pa, int* pb){ if( (array != NULL) && (pa != NULL) && (pb != NULL) ) { int r = 0; int flag = 0; for(int i=0; i<len; i++) { r = r ^ array[i]; } flag = 1 << first_one_bit(r); *pa = 0; *pb = 0; for(int i=0; i<len; i++) { if( array[i] & flag ) { *pa = *pa ^ array[i]; } else { *pb = *pb ^ array[i]; } } }}打印一个 N * N 的矩阵,从首坐标(0, 0) 开始顺时针一次增大。示例: 5 * 5矩阵,其中数字 1 的坐标为(0, 0)1 2 3 4 516 17 18 19 615 24 25 20 714 23 22 21 813 12 11 10 9template <int N>class SpinMatrix{private: int m_matrix[N][N]; struct Offset { int dx; int dy; }; bool isValid(int x, int y);public: SpinMatrix(); void run(); void println(); int scale();};template <int N>SpinMatrix<N>::SpinMatrix(){ for(int i=0; i<N; i++) { for(int j=0; j<N; j++) { m_matrix[i][j] = 0; } }}template <int N>bool SpinMatrix<N>::isValid(int x, int y){ bool ret = true; ret = ret && ((0 <= x) && (x < N)); ret = ret && ((0 <= y) && (y < N)); ret = ret && (m_matrix[x][y] == 0); return ret;}template <int N>void SpinMatrix<N>::run(){ const Offset OFFSET[] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; const int OSLEN = sizeof(OFFSET) / sizeof (OFFSET); int cx = 0; int cy = 0; int cd = 0; int i = 1; do { m_matrix[cx][cy] = i; if( !isValid(cx + OFFSET[cd].dx, cy + OFFSET[cd].dy) ) { cd = (cd + 1) % OSLEN; } cx += OFFSET[cd].dx; cy += OFFSET[cd].dy; i++; }while(i <= NN);}template <int N>void SpinMatrix<N>::println(){ for(int i=0; i<N; i++) { for(int j=0; j<N; j++) { cout << m_matrix[i][j] << ‘\t’; } cout << endl; } cout << endl;}template <int N>int SpinMatrix<N>::scale(){ return N;} ...

March 16, 2019 · 4 min · jiezi

MDK 链接脚本 sct测试

main函数调用前程序启动时就是在片内的RAM上面跑。显然,片内RAM不可能很大。所以在这里就需要增加片外的SDRAM。说来就内存。但是片外内存不可能一开始就能跑程序的。一没初始化,二没有设置时间参数,CPU怎么可能知道片外SDRAM的访问(s3c2440 nand启动,是因为集成了一个nand IP内核,小。但是通吃所有Nand)。再者系统启动时的代码地址和运行地址不见得是一样。因此就会产生代码重定位了。怎么解决了?main函数调用前做好前面这些就ok了。__main之前初始化SDRAM以rt1052初始化为例子//startup_MIMXRT1052.sReset_Handler: … LDR R0, =SystemInit BLX R0 … LDR R0, =__main BX R0 …显然是调用__main函数之前调用了SystemInit。查看链接脚本以及*.map文件。反汇编文件*.map文件 __main 0x60002401 Thumb Code 0 entry.o(.ARM.Collect$$$$00000000) _main_stk 0x60002401 Thumb Code 0 entry4.o(.ARM.Collect$$$$00000003) _main_scatterload 0x60002405 Thumb Code 0 entry5.o(.ARM.Collect$$$$00000004) … __scatterload 0x60002861 Thumb Code 28 init.o(.text) __scatterload_rt2 0x60002861 Thumb Code 0 init.o(.text)连接脚本//MIMXRT1052xxxxx_nor_txt_sdram.scf#define m_text_start 0x60002400#define m_text_size 0x03FFDC00#define m_data_start 0x80000000#define m_data_size 0x01E00000LR_m_text m_interrupts_start m_text_start+m_text_size-m_interrupts_size { ; load region size_region … ER_m_text m_text_start m_text_size { ; load address = execution address * (InRoot$$Sections) .ANY (+RO) } …}汇编文件** Section #4 ‘ER_m_text’ (SHT_PROGBITS) [SHF_ALLOC + SHF_EXECINSTR] … __main _main_stk 0x60002400: f8dfd00c …. LDR sp,__lit__00000000 ; [0x60002410] = 0x20020000 _main_scatterload 0x60002404: f000fa2c ..,. BL __scatterload ; 0x60002860可以看到 __main地址为0x60002400。置放在ER_m_text 节区的。然后在汇编文件中可以看,_main_stk设置好SP指针。跳转到__scatterload。有关__scatterload详细内容。在这里看,不细说。__scatterload 会将FLASH中的RW-data复制到RAM中。__scatterload此时在ER_m_text节区。怎么进行重定位的待续参考资料MDK的编译过程及文件类型全解 https://flash-rtd.readthedocs.io/zh_CN/latest/STM32 _main 里做了什么 http://elmagnifico.me/2017/04/01/STM32-Startup-_main/ ...

March 16, 2019 · 1 min · jiezi

【笔试题精选】_2

考虑函数原型 void hello(int a, int b=7, char pszC=""), 下面的函数调用中属于不合法调用的是?A. hello(5)B. hello(5, 8)C. hello(6, “#")D. hello(0, 0, “#")答案:【C】说明:C++ 函数参数的扩展一个有 800 个节点的完全二叉树,问有多少个叶子节点?A. 100B. 200C. 400D. 无法确定说明:二叉树的深层特性性质 1性质 2性质 3性质 4性质 5若 6 元素为 A.B.C.D.E.F 出栈顺序为 B.D.C.F.E.A,则栈的最小容量为?A. 3B. 4C. 5D. 6答案:【A】说明:排序算法的稳定是指,关键码相同的记录顺序前后相对位置不发生改变,下面哪种排序顺序算法是不稳定的?A. 插入排序B. 冒泡排序C. 快速排序D. 归并排序答案:【C】说明:排序算法时间复杂度稳定性选择排序O(n2)不稳定插入排序O(n2)稳定冒泡排序O(n2)稳定希尔排序O(n2/3)不稳定归并排序O(nlogn)稳定快速排序O(nlogn)不稳定如下关于进程的描述不正确的是?A. 进程在退出时会自动关闭自己打开的所有文件B. 进程在退出时会自动关闭自己打开的网络链接C. 进程在退出时会自动销毁自己创建的所有线程D. 进程在退出时会自动销毁自己打开的共享内存答案:【A B D】在一个 cpp 文件里面,定义了一个 static 类型的全局变量,下面一个正确的描述是?A. 只能在该 cpp 所在的编译模块中使用该变量B. 该变量的值是不可改变的C. 该变量不能在类的成员函数中引用D. 该变量只能是基本类型(如 int, char)不能是 C++ 类型答案:【A】说明:C中的static下面有关重载函数的说法中正确的是?A. 重载函数必须有不同的返回值类型B. 重载函数形参个数必须不同C. 重载函数必须有不同的形参列表D. 重载函数名可以不同答案:【C】说明:函数重载分析-上函数重载分析-下某火车站要通过一条栈道(先进后出)来调换进入车站的列车顺序,若进站的列车顺序为A、B、C,则下列哪个出站顺序不可能?A. ABCB. ACBC. CABD. CBA答案:【C】下面哪种情况下, B 不能隐式转换为 A ?A. class B:public A{};B. class A:public B{};C. class B{operator A();};D. class A{A(const B&)};答案:【B】分析下面程序的运行结构#include<iostream.h>class CBase{ public: CBase(){ cout<<“constructing CBase class”<<endl; } ~CBase(){ cout<<“destructing CBase class”<<endl; }};class CSub : public CBase{ public: CSub(){cout<<“constructing CSub class”<<endl;} ~CSub(){cout<<“destructing CSub class”<<endl;}};void main(){ CSub obj;}A. constructing CSub class constructing CBase class destructing CSub class destructing CBase class B. constructing CBase class constructing CSub class destructing CBase class destructing CSub class C. constructing CBase class constructing CSub class destructing CSub class destructing CBase class D. constructing CSub class constructing CBase class destructing CBase class destructing CSub class答案:【C】两个字符串 char a, char b, 输出 b 在 a 中的位置次序void output_postion(const char* a, const char* b);如:a = “abdbcc” b = “abc” b 在 a 中的位置次序为 014 015 034 035 ...

March 15, 2019 · 1 min · jiezi

【笔试题精选】_1

32位机上根据下面的代码,问哪些说法是正确的?siged char a = 0xe;unsigned int b = a;unsigned char c = a;A. a > 0 && c > 0B. a == c 为真C. b 的十六进制表示为: 0xffffffE0D. 上面都不对答案: 【C】输出:a - dec: -32b - hex: ffffffe0 // 高字节补符号位c - dex: 224说明:有符号与无符号下面哪些选项可以编译通过?int i;char a[10];string f();string g(string& str);A. if(!!i){f();}B. g(f());C. a=a+1;D.g(“abc”);答案:【A】说明:B. 只有 const 引用才可以被临时变量(对象)初始化(string f() 将返回一个临时string对象)C. 数组名可看作指向第一个元素的常量指针,无法进行赋值操作D. “abc” ==> const char* 类型int a[10]; 问下面哪些地址不可以表示a[1]的地址?A. a + sizeof(int)B. &a[0] + 1C. (int*)&a + 1D. (int*)((char*)&a + sizeof(int))答案:【A】说明:Type* p;p+n; <–> (unsigned int)p + n * sizeof(Type)数组名可看作指向第一个元素的常量指针a 类型 ==> int*&a 类型 ==> int()[10]a[0] 类型 ==> intA. a + sizeof(int) ==> (unsigned int)a + 4 * sizeof(int)B. &a[0] + 1 ==> (unsigned int)(&a[0]) + 1 * sizeof(int) C. (int)&a + 1 ==> a + 1 ==> (unsigned int)a + 1 * sizeof(int)D. (int*)((char*)&a + sizeof(int)) ==> (int*)((unsigend int)a + 4 * sizeof(char))下面的数据存放在哪些存储区?int main(){ char* p = “hello, word”; return 0;}A. 代码段B. 栈C. 常量取D. 堆答案:【B C】说明:栈: p常量区:“hello, word"下面哪些函数调用必须进入内核才能完成?A. fopenB. exitC. memcpyD. strlen答案:【A B】说明:A. 打开设备、文件。将触发设备驱动程序的调用,驱动程序运行于内核中。B. 退出进程。由内核管理。C. 内核空间可完成。D. 内核空间可完成。死锁发生的必要条件?A. 互斥条件B. 请求和保持C. 不可剥夺D. 循环等待答案:【A B C D】有两个线程,最初 n=0, 一个线程执行 n++; n++; 另一个执行 n+=2; 问,最后可能的 n 值?A. 1B. 2C. 3D. 4答案:【B C D】说明:++ 非原子操作,实际会对应多条汇编语句,其中每一汇编语句执行完成后都可能被打断。例 n++ 分解三步:取n值,加1,写 n 值回内存。 B. 线程1 n++; 线程1 n++ 将要写 n(2) 值回内存之前被线程2打断,此时线程2完成n++(1+2),回写内存3,线程1继续运行,回写内存2 ==> 2C. 线程1 n++;线程2 n+=2, 将要回写n(3)值回内存之前被线程1打断,线程1 n++,回写内存2,线程2继续执行,回写内存3 ==>3 D. 线程1 n++, n++, 线程2 n+=2 ==> 4 下面哪些说法正确?A. 数组和链表都可以随机访问B. 数组的插入和删除可以达到 O(1)C. 哈希表无法进行范围查找D. 二叉树无法进行线性访问答案:【C】说明:A. 链表只可以进行顺序访问B. 数组的插入和删除伴随着相应元素的移动 O(n)D. 二叉树可以进行线索化后进行线性访问基于比较排序的时间复杂度下限是多少?A. O(n)B. O(n^2)C. O(nlogn)D. O(logn)答案:【C】说明:对于下列程序,在一个 big endia 的32位机器上, b 的结果是?unsigned int a = 0x1234;char b = ((char)&a);A. 0x12B. 0x34C. 0x00D. 程序崩溃答案:【C】说明:大端:数据高位,保存在低地址处;数据低位,保存在高地址处编写函数求两个整数 a 和 b 之间的较大值。要求不能使用 if,while, switch, for, ?; 以及任何的比较语句。int max(int a, int b){ int d = a - b; int flag = ((unsigned int)d) >> 31; int array[] = {a, b}; return array[flag];}(unsigned int)d 原因: 有符号整型数的右移操作,低位被移除,高位补符号位。 ...

March 15, 2019 · 2 min · jiezi

T-Tree、T*-Tree的理解、实现与简单的内存数据库应用

章节目录T*-tree的介绍T*-tree节点与C语言实现T*-tree的插入、删除、查找与旋转实现简单的key-value内存数据库参考文献T-tree和T*-tree极为相似,他们的不同主要是T×-tree的节点结构比T-tree多了一个指向successor的指针位,指向successor的指针的存在是的树的寻找和遍历的时间复杂度.注:本文中关于ttree的head file :ttree.h和ttree_defs.h来源于Dan Kruchinin <dkruchinin@acm.org>,Github:dkruchinin/libttreeT*-tree的介绍在计算机科学中,T-tree是一种二叉树,它有一个左子树和一个右子树,由主存储器数据库使用,例如Datablitz,EXtremeDB,MySQL Cluster,Oracle TimesTen和MobileLite。T树是一种平衡的索引树数据结构,针对索引和实际数据都完全保存在内存中的情况进行了优化,就像B树是针对面向块的辅助存储设备(如硬盘)上的存储而优化的索引结构。 T树寻求获得内存树结构(如AVL树)的性能优势,同时避免它们常见的大存储空间开销。T树不保留索引树节点本身内的索引数据字段的副本。 相反,它们利用了这样的事实:实际数据总是与索引一起在主存储器中,因此它们只包含指向实际数据字段的指针。虽然T树以前被广泛用于主存数据库,但最近的研究表明它们在现代硬件上的表现并不比B树好。主要原因是现代的内存和硬盘的速度差异越来越大了,内存访问速度比硬盘访问速度快,并且CPU的核心缓存容量也越来越大。T*-tree的节点结构与C语言实现T树节点通常由指向父节点,左右子节点,有序数据指针数组和一些额外控制数据的指针组成。 具有两个子树的节点称为内部节点(internal nodes),没有子树的节点称为叶子节点(leaf nodes),而仅具有一个子树的节点称为半叶节点(half-leaf nodes)。 如果值在节点的当前最小值和最大值之间,则该节点称为(bounding node)。对于每个内部节点,它的左子树中会有一个叶子节点或半叶子节点中有一个predecessor(称为最大下限-GLB(Greatest Lower Bound)),还在右子树中包含其最大数据值的后继者(LUB-lower upper bound)的节点(包含GLB或LUP的节点或许距离参照内部节点的距离很远,但也有可能恰好相邻。正因为T-tree的每个节点是有序的,并且不同节点之间保证左子树的数据都比节点数据的最小值小,右子树的数据都比节点数据的最大值大,因此B-tree最左边的节点中最左边的数据是整棵树最小的数据,最右边的节点中的最大值是整棵树最大的数据。叶子和半叶节点中的数据元素量在1~最大元素量之间,内部节点将其占用保持在预定义的最小元素量和最大元素数之间。如图:T-tree,T-treenode的插入、删除、旋转和查找代码来自于:Github:dkruchinin/libttree typedef struct ttree_node { struct ttree_node *parent; /< Pointer to node’s parent */ struct ttree_node *successor; /< Pointer to node’s soccussor */ union { struct ttree_node *sides[2]; struct { struct ttree_node *left; /< Pointer to node’s left child */ struct ttree_node *right; /< Pointer to node’s right child */ }; }; union { uint32_t pad; struct { signed min_idx :12; /< Index of minimum item in node’s array */ signed max_idx :12; /< Index of maximum item in node’s array */ signed bfc :4; /< Node’s balance factor */ unsigned node_side :4; /< Node’s side(TNODE_LEFT, TNODE_RIGHT or TNODE_ROOT) / }; };T-tree的插入、删除、查找与旋转插入int ttree_insert(Ttree *ttree, void item){ TtreeCursor cursor; / * If the tree already contains the same key item has and * tree’s wasn’t allowed to hold duplicate keys, signal an error. */ if (ttree_lookup(ttree, ttree_item2key(ttree, item), &cursor) && ttree->keys_are_unique) { return -1; } ttree_insert_at_cursor(&cursor, item); return 0;}void ttree_insert_at_cursor(TtreeCursor *cursor, void *item){ Ttree *ttree = cursor->ttree; TtreeNode *at_node, *n; TtreeCursor tmp_cursor; void key; TTREE_ASSERT(cursor->ttree != NULL); //TTREE_ASSERT(cursor->state == CURSOR_PENDING); key = ttree_item2key(ttree, item); n = at_node = cursor->tnode; if (!ttree->root) { / The root node has to be created. / at_node = allocate_ttree_node(ttree); at_node->keys[first_tnode_idx(ttree)] = key; at_node->min_idx = at_node->max_idx = first_tnode_idx(ttree); ttree->root = at_node; tnode_set_side(at_node, TNODE_ROOT); ttree_cursor_open_on_node(cursor, ttree, at_node, TNODE_SEEK_START); return; } if (cursor->side == TNODE_BOUND) { if (tnode_is_full(ttree, n)) { / * If node is full its max item should be removed and * new key should be inserted into it. Removed key becomes * new insert value that should be put in successor node. */ void tmp = n->keys[n->max_idx–]; increase_tnode_window(ttree, n, &cursor->idx); n->keys[cursor->idx] = key; key = tmp; ttree_cursor_copy(&tmp_cursor, cursor); cursor = &tmp_cursor; / * If current node hasn’t successor and right child * New node have to be created. It’ll become the right child * of the current node. / if (!n->successor || !n->right) { cursor->side = TNODE_RIGHT; cursor->idx = first_tnode_idx(ttree); goto create_new_node; } at_node = n->successor; / * If successor hasn’t any free rooms, new value is inserted * into newly created node that becomes left child of the current * node’s successor. / if (tnode_is_full(ttree, at_node)) { cursor->side = TNODE_LEFT; cursor->idx = first_tnode_idx(ttree); goto create_new_node; } / * If we’re here, then successor has free rooms and key * will be inserted to one of them. */ cursor->idx = at_node->min_idx; cursor->tnode = at_node; } increase_tnode_window(ttree, at_node, &cursor->idx); at_node->keys[cursor->idx] = key; cursor->state = CURSOR_OPENED; return; }create_new_node: n = allocate_ttree_node(ttree); n->keys[cursor->idx] = key; n->min_idx = n->max_idx = cursor->idx; n->parent = at_node; at_node->sides[cursor->side] = n; tnode_set_side(n, cursor->side); cursor->tnode = n; cursor->state = CURSOR_OPENED; fixup_after_insertion(ttree, n, cursor);}删除void *ttree_delete(Ttree *ttree, void *key){ TtreeCursor cursor; void *ret; ret = ttree_lookup(ttree, key, &cursor); if (!ret) { return ret; } ttree_delete_at_cursor(&cursor); return ret;}void *ttree_delete_at_cursor(TtreeCursor *cursor){ Ttree *ttree = cursor->ttree; TtreeNode *tnode, n; void ret; TTREE_ASSERT(cursor->ttree != NULL); TTREE_ASSERT(cursor->state == CURSOR_OPENED); tnode = cursor->tnode; ret = ttree_key2item(ttree, tnode->keys[cursor->idx]); decrease_tnode_window(ttree, tnode, &cursor->idx); cursor->state = CURSOR_CLOSED; if (UNLIKELY(cursor->idx > tnode->max_idx)) { cursor->idx = tnode->max_idx; } / * If after a key was removed, T-tree node contains more than * minimum allowed number of items, the proccess is completed. / if (tnode_num_keys(tnode) > min_tnode_entries(ttree)) { return ret; } if (is_internal_node(tnode)) { int idx; / * If it is an internal node, we have to recover number * of items from it by moving one item from its successor. / n = tnode->successor; idx = tnode->max_idx + 1; increase_tnode_window(ttree, tnode, &idx); tnode->keys[idx] = n->keys[n->min_idx++]; if (UNLIKELY(cursor->idx > tnode->max_idx)) { cursor->idx = tnode->max_idx; } if (!tnode_is_empty(n) && is_leaf_node(n)) { return ret; } / * If we’re here, then successor is either a half-leaf * or an empty leaf. / tnode = n; } if (!is_leaf_node(tnode)) { int items, diff; n = tnode->left ? tnode->left : tnode->right; items = tnode_num_keys(n); / * If half-leaf can not be merged with a leaf, * the proccess is completed. / if (items > (ttree->keys_per_tnode - tnode_num_keys(tnode))) { return ret; } if (tnode_get_side(n) == TNODE_RIGHT) { / * Merge current node with its right leaf. Items from the leaf * are placed after the maximum item in a node. */ diff = (ttree->keys_per_tnode - tnode->max_idx - items) - 1; if (diff < 0) { memcpy(tnode->keys + tnode->min_idx + diff, tnode->keys + tnode->min_idx, sizeof(void *) * tnode_num_keys(tnode)); tnode->min_idx += diff; tnode->max_idx += diff; if (cursor->tnode == tnode) { cursor->idx += diff; } } memcpy(tnode->keys + tnode->max_idx + 1, n->keys + n->min_idx, sizeof(void ) * items); tnode->max_idx += items; } else { / * Merge current node with its left leaf. Items the leaf * are placed before the minimum item in a node. */ diff = tnode->min_idx - items; if (diff < 0) { register int i; for (i = tnode->max_idx; i >= tnode->min_idx; i–) { tnode->keys[i - diff] = tnode->keys[i]; } tnode->min_idx -= diff; tnode->max_idx -= diff; if (cursor->tnode == tnode) { cursor->idx -= diff; } } memcpy(tnode->keys + tnode->min_idx - items, n->keys + n->min_idx, sizeof(void ) * items); tnode->min_idx -= items; } n->min_idx = 1; n->max_idx = 0; tnode = n; } if (!tnode_is_empty(tnode)) { return ret; } / if we’re here, then current node will be removed from the tree. */ n = tnode->parent; if (!n) { ttree->root = NULL; free(tnode); return ret; } n->sides[tnode_get_side(tnode)] = NULL; fixup_after_deletion(ttree, tnode, NULL); free(tnode); return ret;}查找void *ttree_lookup(Ttree *ttree, void *key, TtreeCursor *cursor){ TtreeNode *n, *marked_tn, *target; int side = TNODE_BOUND, cmp_res, idx; void item = NULL; enum ttree_cursor_state st = CURSOR_PENDING; / * Classical T-tree search algorithm is O(log(2N/M) + log(M - 2)) * Where N is total number of items in the tree and M is a number of * items per node. In worst case each node on the path requires 2 * comparison(with its min and max items) plus binary search in the last * node(bound node) excluding its first and last items. * * Here is used another approach that was suggested in * “Tobin J. Lehman , Michael J. Carey, A Study of Index Structures for * Main Memory Database Management Systems”. * It reduces O(log(2N/M) + log(M - 2)) to true O(log(N)). * This algorithm compares the search * key only with minimum item in each node. If search key is greater, * current node is marked for future consideration. / target = n = ttree->root; marked_tn = NULL; idx = first_tnode_idx(ttree); if (!n) { goto out; } while (n) { target = n; cmp_res = ttree->cmp_func(key, tnode_key_min(n)); if (cmp_res < 0) side = TNODE_LEFT; else if (cmp_res > 0) { marked_tn = n; / mark current node for future consideration. / side = TNODE_RIGHT; } else { / ok, key is found, search is completed. / side = TNODE_BOUND; idx = n->min_idx; item = ttree_key2item(ttree, tnode_key_min(n)); st = CURSOR_OPENED; goto out; } n = n->sides[side]; } if (marked_tn) { int c = ttree->cmp_func(key, tnode_key_max(marked_tn)); if (c <= 0) { side = TNODE_BOUND; target = marked_tn; if (!c) { item = ttree_key2item(ttree, tnode_key_max(target)); idx = target->max_idx; st = CURSOR_OPENED; } else { / make internal binary search / struct tnode_lookup tnl; tnl.key = key; tnl.low_bound = target->min_idx + 1; tnl.high_bound = target->max_idx - 1; item = lookup_inside_tnode(ttree, target, &tnl, &idx); st = (item != NULL) ? CURSOR_OPENED : CURSOR_PENDING; } goto out; } } / * If we’re here, item wasn’t found. So the only thing * needs to be done is to determine the position where search key * may be placed to. If target node is not empty, key may be placed * to its min or max positions. */ if (!tnode_is_full(ttree, target)) { side = TNODE_BOUND; idx = ((marked_tn != target) || (cmp_res < 0)) ? target->min_idx : (target->max_idx + 1); st = CURSOR_PENDING; }out: if (cursor) { ttree_cursor_open_on_node(cursor, ttree, target, TNODE_SEEK_START); cursor->side = side; cursor->idx = idx; cursor->state = st; } return item;}旋转static void __rotate_single(TtreeNode **target, int side){ TtreeNode *p, *s; int opside = opposite_side(side); p = *target; TTREE_ASSERT(p != NULL); s = p->sides[side]; TTREE_ASSERT(s != NULL); tnode_set_side(s, tnode_get_side(p)); p->sides[side] = s->sides[opside]; s->sides[opside] = p; tnode_set_side(p, opside); s->parent = p->parent; p->parent = s; if (p->sides[side]) { p->sides[side]->parent = p; tnode_set_side(p->sides[side], side); } if (s->parent) { if (s->parent->sides[side] == p) s->parent->sides[side] = s; else s->parent->sides[opside] = s; } target = s;}/ * There are two cases of single rotation possible: * 1) Right rotation (side = TNODE_LEFT) * [P] [L] * / \ / \ * [L] x1 => x2 [P] * / \ / \ * x2 x3 x3 x1 * * 2) Left rotation (side = TNODE_RIHGT) * [P] [R] * / \ / \ * x1 [R] => [P] x2 * / \ / \ * x3 x2 x1 x3 */static void rotate_single(TtreeNode **target, int side){ TtreeNode *n; __rotate_single(target, side); n = (target)->sides[opposite_side(side)]; / * Recalculate balance factors of nodes after rotation. * Let X was a root node of rotated subtree and Y was its * child. After single rotation Y is new root of subtree and X is its child. * Y node may become either balanced or overweighted to the * same side it was but 1 level less. * X node scales at 1 level down and possibly it has new child, so * its balance should be recalculated too. If it still internal node and * its new parent was not overwaighted to the opposite to X side, * X is overweighted to the opposite to its new parent side, * otherwise it’s balanced. If X is either half-leaf or leaf, * balance racalculation is obvious. */ if (is_internal_node(n)) { n->bfc = (n->parent->bfc != side2bfc(side)) ? side2bfc(side) : 0; } else { n->bfc = !!(n->right) - !!(n->left); } (*target)->bfc += side2bfc(opposite_side(side)); TTREE_ASSERT((abs(n->bfc < 2) && (abs((target)->bfc) < 2)));}/ * There are two possible cases of double rotation: * 1) Left-right rotation: (side == TNODE_LEFT) * [P] [r] * / \ / \ * [L] x1 [L] [P] * / \ => / \ / \ * x2 [r] x2 x4 x3 x1 * / \ * x4 x3 * * 2) Right-left rotation: (side == TNODE_RIGHT) * [P] [l] * / \ / \ * x1 [R] [P] [R] * / \ => / \ / \ * [l] x2 x1 x3 x4 x2 * / \ * x3 x4 */static void rotate_double(TtreeNode **target, int side){ int opside = opposite_side(side); TtreeNode *n = (target)->sides[side]; __rotate_single(&n, opside); / * Balance recalculation is very similar to recalculation after * simple single rotation. */ if (is_internal_node(n->sides[side])) { n->sides[side]->bfc = (n->bfc == side2bfc(opside)) ? side2bfc(side) : 0; } else { n->sides[side]->bfc = !!(n->sides[side]->right) - !!(n->sides[side]->left); } TTREE_ASSERT(abs(n->sides[side]->bfc) < 2); n = n->parent; __rotate_single(target, side); if (is_internal_node(n)) { n->bfc = ((target)->bfc == side2bfc(side)) ? side2bfc(opside) : 0; } else { n->bfc = !!(n->right) - !!(n->left); } / * new root node of subtree is always ideally balanced * after double rotation. */ TTREE_ASSERT(abs(n->bfc) < 2); (*target)->bfc = 0;}static void rebalance(Ttree *ttree, TtreeNode **node, TtreeCursor *cursor){ int lh = left_heavy(*node); int sum = abs((*node)->bfc + (node)->sides[opposite_side(lh)]->bfc); if (sum >= 2) { rotate_single(node, opposite_side(lh)); goto out; } rotate_double(node, opposite_side(lh)); / * T-tree rotation rules difference from AVL rules in only one aspect. * After double rotation is done and a leaf became a new root node of * subtree and both its left and right childs are half-leafs. * If the new root node contains only one item, N - 1 items should * be moved into it from one of its childs. * (N is a number of items in selected child node). */ if ((tnode_num_keys(*node) == 1) && is_half_leaf((*node)->left) && is_half_leaf((*node)->right)) { TtreeNode n; int offs, nkeys; / * If right child contains more items than left, they will be moved * from the right child. Otherwise from the left one. */ if (tnode_num_keys((*node)->right) >= tnode_num_keys((node)->left)) { / * Right child was selected. So first N - 1 items will be copied * and inserted after parent’s first item. */ n = (*node)->right; nkeys = tnode_num_keys(n); (*node)->keys[0] = (*node)->keys[(*node)->min_idx]; offs = 1; (*node)->min_idx = 0; (*node)->max_idx = nkeys - 1; if (!cursor) { goto no_cursor; } else if (cursor->tnode == n) { if (cursor->idx < n->max_idx) { cursor->tnode = *node; cursor->idx = (node)->min_idx + (cursor->idx - n->min_idx + 1); } else { cursor->idx = first_tnode_idx(ttree); } } } else { / * Left child was selected. So its N - 1 items * (starting after the min one) * will be copied and inserted before parent’s single item. / n = (node)->left; nkeys = tnode_num_keys(n); (node)->keys[ttree->keys_per_tnode - 1] = (node)->keys[(node)->min_idx]; (node)->min_idx = offs = ttree->keys_per_tnode - nkeys; (node)->max_idx = ttree->keys_per_tnode - 1; if (!cursor) { goto no_cursor; } else if (cursor->tnode == n) { if (cursor->idx > n->min_idx) { cursor->tnode = node; cursor->idx = (node)->min_idx + (cursor->idx - n->min_idx); } else { cursor->idx = first_tnode_idx(ttree); } } n->max_idx = n->min_idx++; }no_cursor: memcpy((node)->keys + offs, n->keys + n->min_idx, sizeof(void ) * (nkeys - 1)); n->keys[first_tnode_idx(ttree)] = n->keys[n->max_idx]; n->min_idx = n->max_idx = first_tnode_idx(ttree); }out: if (ttree->root->parent) { ttree->root = node; }}实现简单的key-value内存数据库实现简单的key-value内存数据库,用hashtable来链接key-value的关系。key不光插入到ttree中,而且还存到hash-table中。hash_table采用了macro:hash-table(uthash.h)`uthash.h的帮助文档:macro:uthash.h帮助文档hashkey-value对的插入:插入之前先HASH_FIND_INT看看,key-value存不存在,如果不存在则可以插,存在的话不能插入。void add_user(int user_id, char name) { struct my_struct s; HASH_FIND_INT(users, &user_id, s); / id already in the hash? / if (s==NULL) { s = (struct my_struct )malloc(sizeof s); s->id = user_id; HASH_ADD_INT( users, id, s ); / id: name of key field / } strcpy(s->name, name);}解析存有key-value格式的文件找到一个key-value格式的实例:xlarge.delfopen()读取文件,读完之后 fclose()关闭。因为待会儿要用strtok来拆开每一行,所以malloc个file_line FILE * fp; fp = fopen("/home/vory/programing/c/key_value_mmdb/xlarge.del",“r”); file_line = malloc(1000 * sizeof(char)); memset(file_line, 0, 1000 * sizeof(char)); …… fclose(fp); fgets获取每一行: char * buf; buf = malloc(sizeof(input)); memset(buf,0,sizeof(input)); while(fgets(input ,256,fp)){ strcpy(buf,input); …… }strtok切割每一行为若干字符串。strtok将目标字符串的符合delim中的元素全部替换为'0’,strtok用了之后,原来的目标代码就被破坏了,因此,新malloc一个接受复制的字符串,将目标字符串strcpy()之后,对复制的字符串进行strtok()操作。用完之后free()。 strcpy(buf,source_string); token = strtok(buf,delim);// printf("%s\n",token); parameter[parametercount] = malloc(sizeof(input)); strcpy(parameter[parametercount],token); parametercount++; token = strtok(NULL,delim);// printf("%s\n",token); parameter[parametercount] = malloc(sizeof(input)); strcpy(parameter[parametercount],token); 实例的xlarge.del 文件的内容大概是这样:每一行分成两部分,KEY 和VALUE被逗号隔开着。41231234,“Teenage Caveman"3061234,“Banger Sisters, The"18861234,“Hope Floats"29381234,“No Looking Back"1288,“Erotic Confessions: Volume 8"2954,“Normal Life"43901234,“Utomlyonnye solntsem"20801234,“Island of Dr. Moreau, The"3019,“One Hell of a Guy"6712341,“Adventures of Pluto Nash, The"33031234,“Pronto"34701234,“Ripper, The"106612341,“Devotion"39481234,“Starship Troopers"32381234,“Polish Wedding"30551234,“Oscar and Lucinda"42391,“Tomcats"1661123411,“Gojira ni-sen mireniamu"10611234,“Devil in a Blue Dress"61612341,“Bully"102612341,“Defenders: Taking the First, The"1650,“Go Fish"43512341,“Black Rose of Harlem"解析从文件读来的每一行:每一行最多解析为2个参数([KEY] [VALUE])。void parse_file(char * string){ char * buf; char * delim; delim = NULL; delim = “,”; char * token; token = NULL; buf = malloc(1000sizeof(char)); memset(buf,0, 1000sizeof(char)); if (!parf0){ parf0 =malloc(500sizeof(char)); } memset(parf0,0, 500sizeof(char)); if (!parf1){ parf1 =malloc(500sizeof(char)); } memset(parf1,0, 500sizeof(char)); strcpy(buf, string); token = strtok(buf, delim); if(token != NULL) { strcpy(parf0, token); } token = strtok(NULL, delim); if (token != NULL){ strcpy(parf1, token); } free(buf);}strtol将字符型的数据转换成long int型:long int strtol(const char nptr,char endptr,int base);strtol不仅可以识别十进制整数,还可以识别其它进制的整数,取决于base参数,base为10,则识别十进制整数。 all_items[bcount].key = strtol(parf0,NULL ,10); bcount++; hash_user_id = strtol(parf0,NULL ,10); strcpy(hash_name,parf1);解析从命令行输入的命令([COMMANDS] [KEY] [VALUE])从文件读取每一行是 [KEY] [VALUE]的格式,但是从命令行读取是[COMMANDS] [KEY] [VALUE]的格式。我将copy_string,par1,par2,par3定义在了别处。这样就可以解析从stdin输入的句子了,被空格隔开的句子最多分为发出3路给par1,par2,par3。如果stdin输入的句子只包含了一个空格(也就是含有[COMMANDS] [KEY]的结构)则只能被分发为2路给par1和par2.malloc()之后要free(),我在别处free() 了。void parseinput(char string){ char * delim; delim = " “; copy_string = malloc(100sizeof(char)); memset(copy_string,0,100 sizeof(char)); char * token; token = NULL; par1 = malloc(50sizeof(char)); par2 = malloc(50sizeof(char)); par3 = malloc(50sizeof(char)); memset(par1,0,50sizeof(char)); memset(par2,0,50sizeof(char)); memset(par3,0,50sizeof(char)); strcpy(copy_string,string); printf("%s is copystring .\n “,copy_string); printf("%s is string . \n”,string); token = strtok(copy_string,delim); if (token != NULL){ printf("%s is token1 \n”,token); strcpy(par1,token); } token = strtok(NULL,delim); if (token != NULL){ printf("%s is token2 \n”,token); strcpy(par2,token); } token = strtok(NULL,delim); if (token != NULL){ printf("%s is token3 \n”,token); strcpy(par3,token); } free(copy_string);}初始化T-tree#define ttree_init(ttree, num_keys, is_unique, cmpf, data_struct, key_field) _ttree_init(ttree, num_keys, is_unique, cmpf, offsetof(data_struct, key_field))int __ttree_init(Ttree ttree, int num_keys, bool is_unique, ttree_cmp_func_fn cmpf, size_t key_offs);……….. ret = ttree_init(&ttree, 8, false, __cmpfunc, struct item, key); if (ret < 0) { fprintf(stderr, “Failed to initialize T-tree. [ERR=%d]\n”, ret); free(all_items); exit(EXIT_FAILURE); }将读取的每一行插入ttree,并将key-value插入hashtable在一个循环中解析每一行,当真个文件的所有行都读完则跳出循环。 while (fgets(file_line, 1000, fp)) { parse_file(file_line); all_items[bcount].key = strtol(parf0, NULL, 10); hash_name = malloc(500 * sizeof(char)); memset(hash_name, 0, 500 * sizeof(char)); hash_user_id = strtol(parf0, NULL, 10); strcpy(hash_name, parf1); s = find_user(hash_user_id); if (s == NULL) { add_user(hash_user_id, hash_name); } free(hash_name); memset(file_line, 0, 1000 * sizeof(char)); } for (i = 0; i < num_keys; i++) { ret = ttree_insert(&ttree, &all_items[i]); if (ret < 0) { fprintf(stderr, “Failed to insert item %d with key %ld! [ERR=%d]\n”, i, all_items[i].key, ret); free(all_items); exit(EXIT_FAILURE); } } 打印出ttree的所有key for (i = 0; i < num_keys; i++) { printf("%ld “, all_items[i].key); }给ttree的所有key排序从小到大排序,递归实现。 printf("\nSorted keys:\n”); printf(”{ “); tnode = ttree_node_leftmost(ttree.root); while (tnode) { tnode_for_each_index(tnode, i) { printf("%d “, (int ) tnode_key(tnode, i)); } tnode = tnode->successor; } printf(”}\n”);程序结束前free(),释放内存空间ttree_destroy(&ttree);free(all_items);附件&代码github代码所有的代码参考文献[1].Tobin J. Lehman and Michael J. Carey. 1986. A Study of Index Structures for Main Memory Database Management Systems. In Proceedings of the 12th International Conference on Very Large Data Bases (VLDB ‘86), Wesley W. Chu, Georges Gardarin, Setsuo Ohsuga, and Yahiko Kambayashi (Eds.). Morgan Kaufmann Publishers Inc., San Francisco, CA, USA, 294-303.[2].Kong-Rim Choi and Kyung-Chang Kim, “T-tree: a main memory database index structure for real time applications,” Proceedings of 3rd International Workshop on Real-Time Computing Systems and Applications, Seoul, South Korea, 1996, pp. 81-88.doi: 10.1109/RTCSA.1996.554964[3].wikipidia about T-tree[4].An Open-source T-tree Library ...

March 14, 2019 · 13 min · jiezi

教你利用Windows访问控制搞事情

开篇福利FkUpdateWin10自动更新是真的烦人,每次按照网上的步骤禁用自动更新后,不用过多久系统又自动恢复了Update!于是自己研究了访问控制,利用访问控制原理修改服务对应的注册表权限,让系统无法修改服务的状态,达到永久禁用自动更新的效果!目前为止尚未发现Bug,所以共享给大家使用!只希望大家给文章点点赞!永久禁用Windows自动更新 - 下载连接FileLocker利用访问控制原理修改文件和上层目录的权限,使得文件不可被删除。目前为止也只能防止文件误删!之后可能会添加防止移动、防止修改等功能!防止文件误删 - 下载链接概念普及常用术语ACL(Access Control List) - Windows访问控制列表DACL(Discretionary Access Control List) - 任意访问控制列表SACL(System Access Control List) - 系统访问控制列表ACE(Access Control Entries) - 访问控制条目SD(Security Descriptor) - 安全描述符SID(Security Identifier) - 安全标识符AccessToken - 访问令牌详细解释从简单到复杂的依次解释SID,用于标识用户,组和计算机帐户,首次创建帐户时会获得一个唯一的SID用于标识该账户。简单来说就如同每个大学生入学都会分配一个唯一的学号,这个学号就是你的证明ACL,用来说明某个对象的访问权限,由DACL和SACL组成,具体的某项权限称为ACE。简单来说就如同大学校园里的各项规定,任何一个对象都有它特定的规则,假设某个具体对象为教室里的电脑,学生们只能看,而老师们可以操控,这就是教室里的电脑这个对象的访问权限SD,包含与安全对象相关的一些安全信息,包括该对象的所有者和所属组的SID,DACL,SACL以及一组控制位(用于限定所有者SID,DACL和SACL)AccessToken,用来控制对安全对象的访问,访问令牌包含登录会话的安全信息,主要用来标识用户,用户组的权限。系统在用户登录时创建访问令牌,并且该用户执行的每个进程都具有该令牌的副本注意1:SACL主要用于审核和记录访问的,DACL才是具体的权限列表,所以我们平时讲的ACL通常是指DACL注意2:SD是为了方便编程提出的概念(一个结构体而已),实际上操作系统是利用AccessToken和ACL来确定对某个文件、进程等的访问权限权限检查过程每个计算机账户在登录是都会获得一个访问令牌AccessToken,这个令牌会说明当前用户的权限!而每个文件或其它对象都有它自己的访问控制列表ACL,即说明哪些账户拥有哪些权限!当该账户尝试读取或改写某个文件时,操作系统会将当前账户的访问令牌权限和目标文件每个具体的权限(ACE)按顺序作比较(只与SID相同的ACE进行比较),直到发生以下事件:一、拒绝访问的ACE明确拒绝对线程访问令牌中列出的其中一个受托者请求的任何访问权限二、线程访问令牌中列出的受托者的一个或多个允许访问的ACE明确授予所有请求的访问权限三、已检查所有ACE,并且仍然至少有一个未明确允许的请求访问权限,在这种情况下,隐式拒绝访问注意:访问控制列表ACL中的ACE有几大原则(拒绝大于允许、权限最小化、权限继承性以及权限累加)动手实践查看文件ACL讲了半天ACL是不是感觉太抽象了,来实际看看什么是ACL吧!你只需要在任意文件上右键-属性-安全-高级就能看到该文件的ACL了!其中权限选项卡中就是DACL,审核选项卡中就是SACL,所有者拥有对DACL的完全控制权其中SYSTEM用户组拥有对该文件的读取权限,这样一条具体的某个用户的某项权限就是ACL中的访问控制条目(ACE)简单修改权限修改当前用户组对某个文件只有读取权限,假如当前用户组是管理员的话还需要修改Administrators组的权限!第一步右键-属性-安全并选中当前用户点击编辑第二步只勾选允许-读取,发现不可勾选第三步禁用继承,必须要禁用继承否则不可更改,点击高级-更改权限并取消勾选包括可从该对象的父项继承的权限,弹窗选择添加,然后确定第四步重复第二步,并更改Administrators组的权限第五步尝试改写该文件,会提示没有权限注意:文件夹拥有继承和传播属性,文件拥有继承属性,继承属性很好理解就是直接复制父目录的ACL,传播就是当前文件夹是否允许子文件或子文件夹继承FkUpdate核心讲解服务相关开启关闭服务,必须用到的几个API:OpenSCManagerOpenServiceStartServiceControlServiceQueryServiceStatusChangeServiceConfigCloseServiceHandle注册表ACL相关核心API:GetNamedSecurityInfoSetNamedSecurityInfoSetEntriesInAclGetExplicitEntriesFromAclAllocateAndInitializeSidDeleteAce具体思路禁用自动更新:第一步停止Update服务,第二步Update启动状态改为禁用,第三步Update注册表所有者改为当前用户,第四步禁用继承并添加,第五步修改所有用户组的权限为只读,第六步注册表所有者改为SYSTEM恢复自动更新:第一步Update注册表所有者改为当前用户,第二步删除所有ACL,第三步启用继承,第四步注册表所有者改为SYSTEM,第五步Update服务状态改为自动,第六步启动Update服务BOOL enableUpdate() { changeObjectOwner(updateReg, FALSE); enInherit(updateReg); changeObjectOwner(updateReg, TRUE); changeStartType(updateServ, SERVICE_AUTO_START); startSrv(updateServ); return TRUE;}BOOL disableUpdate() { PACL pOldDACL = NULL, pNewDACL = NULL; DWORD dwRes = 0, dwSize = 0, i = 0; PSECURITY_DESCRIPTOR pSD; SID_NAME_USE eUse = SidTypeUnknown; PEXPLICIT_ACCESS pEa; ULONG uCount; stopSrv(updateServ); changeStartType(updateServ, SERVICE_DISABLED); changeObjectOwner(updateReg, FALSE); // disInheritDelete(updateReg); enInherit(updateReg); disInheritCopy(updateReg); dwRes = GetNamedSecurityInfo(updateReg, SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL, &pSD); if (dwRes != ERROR_SUCCESS) { printf(“GetNamedSecurityInfo Error %u\n”, dwRes); return FALSE; } if (ERROR_SUCCESS == GetExplicitEntriesFromAcl(pOldDACL, &uCount, &pEa)) { for (i = 0; i < uCount; i++) { pEa[i].grfAccessPermissions = GENERIC_READ; } } if (ERROR_SUCCESS != SetEntriesInAcl(uCount, pEa, NULL, &pNewDACL)) { printf(“Failed SetEntriesInAcl\n”); return FALSE; } dwRes = SetNamedSecurityInfo(updateReg, SE_REGISTRY_KEY, DACL_SECURITY_INFORMATION, NULL, NULL, pNewDACL, NULL); if (ERROR_SUCCESS == dwRes) { printf(“Successfully Changed DACL\n”); } changeObjectOwner(updateReg, TRUE); if (pOldDACL) LocalFree(pOldDACL); if (pNewDACL) LocalFree(pNewDACL); if (pSD) LocalFree(pSD); if(pEa) LocalFree(pEa); return TRUE;}FileLocker核心讲解文件相关核心API:GetFileAttributessplitPath(自己封装路径分割)ACL相关核心API:GetNamedSecurityInfoSetNamedSecurityInfoSetEntriesInAclGetExplicitEntriesFromAclAllocateAndInitializeSidDeleteAce核心思路锁文件:第一步获取上层目录的路径,第二步上层目录禁用继承,第三步上层目录设置拒绝删除子文件的属性,第四步当前文件禁用继承,第五步当前文件添加拒绝删除的属性,第六步更改文件所有者为SYSTEM恢复文件:第一步获取上层目录的路径,第二步上层目录启用继承并删除之前的ACL,第三步当前文件启用继承并删除之前的ACL,第四步更改文件的所有者为SYSTEMBOOL lockFile(LPTSTR lpMyFile) { DWORD dwRes, i; PACL pOldDACL = NULL, pNewDACL = NULL; PSECURITY_DESCRIPTOR pSD = NULL; PEXPLICIT_ACCESS pEa; ULONG uCount; CHAR lpFileName[MAX_PATH] = { 0 }; /* 首先得到上层目录路径 / splitPath(lpMyFile, lpFileName); / 上层目录拥有者改为Admin / changeObjectOwner(lpFileName, FALSE); / 禁止上层目录继承 / disInheritCopy(lpFileName); / 保存一份原始DACL / dwRes = GetNamedSecurityInfo(lpFileName, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL, &pSD); if (dwRes != ERROR_SUCCESS) { printf(“GetNamedSecurityInfo Error %u\n”, dwRes); return FALSE; } / 设置拒绝删除子文件夹的属性 / if (ERROR_SUCCESS == GetExplicitEntriesFromAcl(pOldDACL, &uCount, &pEa)) { for (i = 0; i < uCount; i++) { / public enum ACCESS_MASK { READ_FILE = 0x000001, WRITE_FILE = 0x000002, CREATE_SUBDIR = 0x000004, READ_EXT_ATTR = 0x000008, WRITE_EXT_ATTR = 0x000010, EXECUTE = 0x000020, DELETE_DIR = 0x000040, READ_FILE_ATTR = 0x000080, WRITE_FILE_ATTR = 0x000100, DELETE = 0x010000, READ_SD = 0x020000, WRITE_DACL = 0x040000, WRITE_OWNER = 0x080000, SYNCHRONIZE = 0x100000, SHARE_READ = READ_FILE | READ_EXT_ATTR | EXECUTE | READ_FILE_ATTR | READ_SD | SYNCHRONIZE, SHARE_CHANGE = SHARE_READ | WRITE_FILE | CREATE_SUBDIR | WRITE_EXT_ATTR | WRITE_FILE_ATTR | DELETE, SHARE_FULL = SHARE_CHANGE | DELETE_DIR | WRITE_DACL | WRITE_OWNER } / pEa[i].grfAccessPermissions = 0x40; pEa[i].grfAccessMode = DENY_ACCESS; pEa[i].grfInheritance = NO_INHERITANCE; } } if (ERROR_SUCCESS != SetEntriesInAcl(uCount, pEa, pOldDACL, &pNewDACL)) { printf(“Failed SetEntriesInAcl\n”); return FALSE; } / 设置新的DACL / dwRes = SetNamedSecurityInfo(lpFileName, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pNewDACL, NULL); if (dwRes != ERROR_SUCCESS) { printf(“SetNamedSecurityInfo Error %u\n”, dwRes); return FALSE; } / 上层目录拥有者改回System / changeObjectOwner(lpFileName, TRUE); / 当前文件或目录的拥有者改为Admin / changeObjectOwner(lpMyFile, FALSE); / 当前文件或目录禁止继承 / disInheritCopy(lpMyFile); / 保留当前文件或目录的DACL / dwRes = GetNamedSecurityInfo(lpMyFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, &pOldDACL, NULL, &pSD); if (dwRes != ERROR_SUCCESS) { printf(“GetNamedSecurityInfo Error %u\n”, dwRes); return FALSE; } / 设置拒绝属性 / if (ERROR_SUCCESS == GetExplicitEntriesFromAcl(pOldDACL, &uCount, &pEa)) { for (i = 0; i < uCount; i++) { pEa[i].grfAccessPermissions = DELETE; pEa[i].grfAccessMode = DENY_ACCESS; pEa[i].grfInheritance = NO_INHERITANCE; } } if (ERROR_SUCCESS != SetEntriesInAcl(uCount, pEa, pOldDACL, &pNewDACL)) { printf(“Failed SetEntriesInAcl\n”); return FALSE; } / 设置新的DACL / dwRes = SetNamedSecurityInfo(lpMyFile, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pNewDACL, NULL); if (dwRes != ERROR_SUCCESS) { printf(“SetNamedSecurityInfo Error %u\n”, dwRes); return FALSE; } changeObjectOwner(lpMyFile, TRUE); / SECURITY_DESCRIPTOR SD; InitializeSecurityDescriptor(&SD, SECURITY_DESCRIPTOR_REVISION); SetSecurityDescriptorDacl(&SD, TRUE, NULL, FALSE); / if (pOldDACL) LocalFree(pOldDACL); if (pNewDACL) LocalFree(pNewDACL); if (pSD) LocalFree(pSD); if (pEa) LocalFree(pEa); return TRUE;}BOOL recoveryFile(LPTSTR lpMyFile) { CHAR lpFileName[MAX_PATH] = { 0 }; / 首先得到上层目录路径 */ splitPath(lpMyFile, lpFileName); changeObjectOwner(lpFileName, FALSE); enInherit(lpFileName); changeObjectOwner(lpFileName, TRUE); changeObjectOwner(lpMyFile, FALSE); enInherit(lpMyFile); changeObjectOwner(lpMyFile, TRUE); return TRUE;}END ...

March 13, 2019 · 3 min · jiezi

逆序与蛇形输出矩阵

逆序与蛇形输出矩阵逆序输入n,得到nn矩阵,逆时针输出各项元素值,并空格隔开。例,n=4,矩阵就是1 2 3 45 6 7 89 10 11 1213 14 15 16输出的内容就是:1 2 3 4 8 12 16 15 14 13 9 5 6 7 11 10。分析首先,这是一个边界问题,从里到外,我们可以理解为运动问题,即从Matrix[0][0],环绕矩阵外围一直运动到矩阵内部。我们可以定义4个方向,即0/1/2/3(右,下,左,上)。再定义Mark[n][n]=1,每当输出一个数值时,便另Mark[n][n]=0;通过判断边界调整方向。代码块int matrix[N][N];int mark[N][N];int main(){ int n; while(scanf("%d",&n)!=EOF){ int t=1; int i; //初始化 for(i=0;i<n;i++){ for(int j=0;j<n;j++){ matrix[i][j]=(t++); } } for(i=0;i<n;i++){ for(int j=0;j<n;j++){ mark[i][j]=1; } } printf("\n"); int count=nn; int go=0,x=0,y=0; while(count–){ switch(go){ case 0: //右 printf("%d “,matrix[x][y]); mark[x][y]=0; if(mark[x][y+1]==0){ go=1; x++; }else{ y++; } break; case 1: //下 printf("%d “,matrix[x][y]); mark[x][y]=0; if(mark[x+1][y]==0){ go=2; y–; }else{ x++; } break; case 2: //左 printf("%d “,matrix[x][y]); mark[x][y]=0; if(mark[x][y-1]==0){ go=3; x–; }else{ y–; } break; case 3: //上 printf("%d “,matrix[x][y]); mark[x][y]=0; if(mark[x-1][y]==0){ go=0; y++; }else{ x–; } break; } } printf("\n”); } return 0;}蛇形输入n,得到nn矩阵,蛇形输出各项元素值,并空格隔开。例,n=4,矩阵就是1 2 3 45 6 7 89 10 11 1213 14 15 16输出的内容就是:1 2 5 9 6 3 4 7 10 13 14 11 8 12 15 16分析这也是一题边界问题,该矩阵输出有4个方向,即0/1/2/3(右,左下,下,右上),每当到达边界,只需判断下一步大方向即可。代码块int count=nn-1; int go=0,x=0,y=0; printf("%d “,matrix[x][y]); mark[x][y]=0; while(count–){ switch(go){ case 0: //右 y++; printf("%d “,matrix[x][y]); mark[x][y]=0; if(x==0){ go=1; }else if(x==n-1){ go=3; } break; case 1: //左下 if(x<n-1 && y>0){ x++; y–; } printf("%d “,matrix[x][y]); if(y==0 && x==n-1){ go=0; }else if(y==0){ go=2; }else if(x==n-1){ go=0; } break; case 2: //下 x++; printf("%d “,matrix[x][y]); mark[x][y]=0; if(y==0 || x==n-1){ go=3; }else if(y==n-1){ go=1; } break; case 3: //右上 if(x>0 && y<n-1){ x–; y++; } printf("%d “,matrix[x][y]); mark[x][y]=0; if(y==n-1 && x==0){ go=2; }else if(y==n-1){ go=2; }else if(x==0){ go=0; } break; } ...

March 11, 2019 · 2 min · jiezi

统计字符串中数字出现频率

给定一个包含字母和数字的字符串,要求统计出这个字符串中从0到9的数字的个数。字符串仅包含英文字母与阿拉伯数字,长度1<=len(nums)<=1000。实例输入:a11472o5t6输出:0 2 1 0 1 1 1 1 0 0 这里数字1出现了两次,记为2,而2 4 5 6 7各出现一次,记为1,剩下的0 3 8 9都没有出现,记为0。在C语言中,对于char类型的变量c表示的数字字符,可以通过进行运算c-‘0’来得到其所对应的数字值,这是因为C中字符型的数值运算事实上被隐式转换为了int型。int main() { int* nums = (int*) malloc(10 * sizeof(int)); char c; for(int i = 0; i < 10; i++) (nums+i) = 0; while(scanf("%c", &c) == 1) if(c >= ‘0’ && c <= ‘9’) ((nums+(c-‘0’)))++; for(int i = 0; i < 10; i++) printf("%d “, *(nums+i)); free(nums); return 0;}通过malloc获得一个十位的全0列表,同时通过scanf来逐一获取字符串中的字符,并通过ascii码值比较判断是否为数字,,同时将对应位的数字加一。最后则是一个输出过程。当然,由于使用了malloc,所以不能忘记使用free释放这些内存空间。另外值得一提的是由于free函数仅销毁已经不再需要的内存空间而不对指针做变化,我们剩下的指针将仍然存在直到程序结束,但却指向了垃圾内存,为了避免调用这些指针发生不必要的错误,在实际编程中我们可能需要主动将该指针指向NULL。其实这个有个malloc部分我们完全可以用数组代替。甚至有人使用数组直接一次性读入字符串的方法。char str[1001];scanf("%s”, str);int lut[10] = {0, };for (int i = 0; i < strlen(str); ++i) { if (str[i] >= ‘0’ && str[i] <= ‘9’) { ++lut[str[i] - ‘0’]; }}for (int i = 0; i < 10; ++i) { printf("%d “, lut[i]);}题目说明了字符串最长有1000位,这里使用str[1001],别忘了字符串最后需要一位存放\0来表示字符串结尾。这段代码相比前者可读性提高了一点。 ...

March 10, 2019 · 1 min · jiezi

嵌入式C语言自我修养(01):Linux 内核中的 C 语言语法扩展

1.1 Linux 内核驱动中的奇怪语法大家在看一些 GNU 开源软件,或者阅读 Linux 内核、驱动源码时会发现,在 Linux 内核源码中,有大量的 C 程序看起来“怪怪的”。说它是C语言吧,貌似又跟教材中的写法不太一样;说它不是 C 语言呢,但是这些程序确确实实是在一个 C 文件中。此时,你肯定怀疑你看到的是一个“假的 C 语言”!比如,下面的宏定义:#define ftrace_vprintk(fmt, vargs) \do { \ if (__builtin_constant_p(fmt)) { \ static const char trace_printk_fmt __used \ attribute((section("__trace_printk_fmt"))) = \ __builtin_constant_p(fmt) ? fmt : NULL; \ \ __ftrace_vbprintk(THIS_IP, trace_printk_fmt, vargs); \ } else \ __ftrace_vprintk(THIS_IP, fmt, vargs); } while (0)字符驱动的填充:static const struct file_operations lowpan_control_fops = { .open = lowpan_control_open, .read = seq_read, .write = lowpan_control_write, .llseek = seq_lseek, .release = single_release, };内核中实现打印功能的宏定义:#define pr_info(fmt, …) __pr(__pr_info, fmt, ##VA_ARGS)#define pr_debug(fmt, …) __pr(__pr_debug, fmt, ##VA_ARGS)你没有看错,这些其实也是 C 语言,但并不是标准的 C 语言语法,而是我们 Linux 内核使用的 GNU C 编译器扩展的一些 C 语言语法。这些语法在 C 语言教材或资料中一般不会提及,所以你才会似曾相识而又感到陌生,看起来感觉“怪怪的”。我们在做 Linux 驱动开发,或者阅读 Linux 内核源码过程中,会经常遇到这些“稀奇古怪”的用法,如果不去了解这些特殊语法的具体含义,可能就对代码的理解造成一定障碍。本教程,就是带领大家一起去了解 Linux 内核或者 GNU 开源软件中,常用的一些 C 语言特殊语法扩展,扫除阅读 Linux 内核或 GNU 开源软件时,这些扩展特性带给我们的语法阅读障碍和困惑。 1.2 C语言标准和编译器在进入正式课程之前,先给大家普及一下 C 标准的概念。在学习 C 语言时,大家在教材或资料上,或多或少可能见到过“ANSI C”的字眼。可能当时没有太在意,其实“ANSI C” 表示的就是 C 语言标准。什么是 C 语言标准呢?我们生活的现实世界,就是由各种标准构成的,正是这些标准,我们的社会才会有条不紊的运行。比如我们过马路,遵循的交通规则就是一个标准:红灯停,绿灯行,黄灯亮了等一等。当行人和司机都遵循这个默认的标准时,我们的交通系统才会顺畅运行。电脑中的 USB 接口也是一种标准,当大家生产的 USB 产品都遵循 USB 协议这种通信标准时,我们的手机、U 盘、USB 摄像头、USB 网卡才可以在各种电脑设备上互插互拔。2G、3G、4G 也是一种标准,当不同厂家生产的基带芯片都遵循这种通信标准,我们所用的不同品牌、不同操作系统的手机才可能互相打电话、互相发微信、互相给对方点赞。同样,C 语言也有它自己的标准。我们知道,C 语言程序需要通过编译器,编译生成二进制指令,才能在我们的电脑上运行。在 C 语言刚发布的早期,各大编译器厂商开发自己的编译器时,各自开发,各自维护,时间久了,就会变得比较混乱。这就会造成这样一种局面:程序员写的程序,在一个编译器上编译通过,在另一个编译器编译通不过。大家按各自的习惯来,谁也不服谁,就像春秋战国时代:不同的货币、不同的度量衡,不同的文字,都是中国人,因为标准不统一,所以交流起来很麻烦,这样下去也不是办法啊。后来 ANSI(AMERICAN NATIONAL STANDARDS INSTITUTE: 美国国家标准协会,简称 ANSI)出山了,联合 ISO(国际化标准组织)召集各个编译器厂商大佬,各种技术团体,一起喝个茶、开个碰头会,开始启动 C 语言的标准化工作。期间各种大佬之间也是矛盾重重,充满各种争议,但功夫不负有心人,经过艰难的磋商,终于在1989年达成一致,发布了 C 语言标准,后来第二年又做了一些改进。于是,就像秦始皇统一六国、统一文字和度量衡一样,C 语言标准终于问世了!因为是在 1989 年发布的,所以人们一般称其为 C89 或 C90 标准,或者叫做 ANSI C。 1.3 C标准内容C 标准里主要讲了什么?C 标准英文文档,洋洋洒洒几百页,讲了很多东西,但总体归纳起来,主要就是 C 语言编程的一些语法惯例,比如:定义各种关键字、数据类型定义各种运算规则各种运算符的优先级和结合性数据类型转换变量的作用域函数原型函数嵌套层数函数参数个数限制标准库函数C 标准发布后,大家都遵守这个标准:程序员开发程序时,按照这种标准写;编译器厂商开发编译器时,也按照这种标准去解析、翻译程序。不同的编译器厂商支持统一的标准,这样大家写的程序,使用不同的编译器,都可以正确编译、运行,大大提高程序的开发效率,推动了 IT 行业的发展。 1.4 C标准的发展过程C 标准并不是永远不变的,就跟移动通信一样,也是从 2G、3G、4G 到 5G 不断发展变化的。C 标准也经历了下面四个阶段:K&R CANSI CC99C11K&R CK&R C 一般也称为传统 C。在 C 标准没有统一之前,C 语言的作者 Dennis Ritchie 和 Brian Kernighan 合作写了一本书《C 程序设计语言》。早期程序员编程,这本书可以说是绝对权威。这本书很薄,内容精炼,主要介绍了 C 语言的基本使用方法。后来《C 程序设计语言》第二版问世,做了一些修改:比如新增 unsigned int、long int、struct 等数据类型;把运算符 =+/=- 修改为 +=/-=,避免运算符带来的一些歧义和 Bug。这本书可以看作是 ANSI 标准的雏形。但早期的 C 语言还是很简单的,比如还没有定义标准库函数、没有预处理命令等。ANSI CANSI C 是 ANSI(美国国家标准协会)在 K&R C 的基础上,统一了各大编译器厂商的不同标准,并对 C 语言语法和特性做了一些扩展,而发布的一个标准。这个标准一般也叫做 C89/C90,也是目前各种编译器默认支持的 C 语言标准。ANSI C 主要新增了以下特性:增加 signed、volatile、const 关键字增加 void 数据类型增加预处理器命令增加宽字符、宽字符串定义了 C 标准库……C99 标准C99 标准是 ANSI 1999 年在 C89 标准的基础上新发布的一个标准,该标准对 ANSI C 标准做了一些扩充,比如新增一些关键字,支持新的数据类型:布尔型:_Bool复数:_Complex虚数:_Imaginary内联:inline指针修饰符:restrict支持long long、long double数据类型支持变长数组允许对结构体特定成员赋值支持16进制浮点数、float _Complex等数据类型……除此之外,C99 标准也借鉴其它语言的一些优点,对语法和函数做了一系列改进,大大方便了程序员开发程序,比如:变量声明可以放代码块的任何地方。ANSI C 规定变量的声明要全部写在函数语句的最前面,否则就会报编译错误。现在不需要这样写了,哪里需要使用变量,在哪里直接声明使用即可;源程序每行最大支持4095个字节。这个貌似足够用了,没有什么程序能复杂到一行程序有4KB个字符;支持//单行注释。ANSI C使用/**/没有C++的//注释方便,所以 C99 新标准借鉴过来了,也开始支持这种注释方式;标准库新增了一些头文件:如 stdbool.h、complex.h、stdarg.h、fenv.h 等。大家在 C 语言中经常返回的 true、false,其实这也是 C++ 里面定义的 bool 类型。那为什么我们经常这样写,而编器编译程序时没有报错呢,这是因为早期大家编程使用的都是 VC++6.0 系列,是 C++ 编译器。还有一种可能就是有些 IDE 对这个数据类型的数据做了封装。C11 新标准C11 标准是2011年发布的最新 C 语言标准,修改了 C 语言标准的一些 Bug、新增了一些特性:增加 _Noreturn,声明函数无返回值;增加_Generic:支持泛型编程;修改了标准库函数的一些 Bug:如 gets( )函数被 gets_s() 函数代替;新增文件锁功能;支持多线程;……从 C11 标准的修改内容来看,也慢慢察觉到 C 语言未来的发展趋势:C 语言现在也在借鉴现在编程语言的优点,不断添加到自己的标准里面。比如现代编程语言的多线程、字符串、泛型编程等,C 语言最新的标准都支持。但是这样下去,C 语言是不是还能保持她“简单就是美”的优雅特色呢,我们只能慢慢期待了。但至少目前我们不用担心这些,因为 C11 新发布的标准,目前绝大多数编译器还不支持,所以我们暂时还用不到。 1.5 编译器对 C 标准的支持标准是一回事,各种编译器支不支持是另一回事,这一点,大家要搞清楚。这就跟手机一样,不同时期发布的手机对通信标准支持也不一样。早期的手机可能只支持 2G 通信,后来支持 3G,现在发布的新款手机基本上都支持 4G了,而且可以兼容 2G/3G。现在 5G 标准正在研发,快发布了,据说 2019 年发布,2020 年商用。但是目前还没有手机支持 5G 通信,就跟现在没有编译器支持 C11 标准一样。不同编译器,甚至对 C 标准的支持也不一样。有的编译器只支持 ANSI C,这是目前默认的 C 标准。有的编译器可以支持 C99,或者支持 C99 标准的部分特性。目前对 C99 标准支持最好的是 GNU C 编译器,据说可以支持 C99标准99%的新增特性。1.6 编译器对 C 标准的扩展不同编译器,出于开发环境、硬件平台、性能优化的需要,除了支持 C 标准外,还会自己做一些扩展。在51单片机上用 C 语言开发程序,我们经常使用 Keil for C51 集成开发环境。你会发现 Keil for C51 或其他 IDE 里的 C 编译器会对 C 语言标准作很多扩展。比如增加各种关键字:data:RAM 的低128B空间,单周期直接寻址;code:表示程序存储区;bit:位变量,常用来定义单片机的 P0~P3 管脚;sbit:特殊功能位变量;sfr:特殊功能寄存器;reentrant:重入函数声明。如果你在程序中使用以上这些关键字,那么你的程序就只能使用51编译器来编译运行,你使用其它的编译器,比如 VC++6.0,是编译通不过的。同样的道理,GCC 编译器,也对 C 标准做了很多扩展:零长度数组语句表达式内建函数__attribute__特殊属性声明标号元素case 范围…比如支持零长度数组。这些新增的特性,C 标准目前是不支持的,其它编译器也不支持。如果你在程序中定义一个零长度数组:int a[0];只能使用 GCC 编译器才能正确编译,使用 VC++ 6.0编译器编译可能就通不过,因为微软的 C++ 编译器不支持这个特性。 1.7 本教程主要内容在 GNU 开源软件、Linux 内核中会大量使用 GCC 自己扩展的语法,这会对我们理解开源软件、Linux 内核代码带来一定障碍和困扰。本教程主要介绍 GNU C 对 C 标准扩展的一些常用语法和使用。终极目标是看懂 Linux 内核驱动、GNU 开源软件中这些特殊语法的应用,扫除这些特殊语法对我们理解内核代码带来的困扰和障碍。1.8 本教程需要的学习环境在本教程讲解中,会使用一些 arm-linux-gnueabi-gcc 等命令用来编译和反汇编程序。所以在学习本教程之前,确保你的电脑上有如下 Linux 环境或源代码:Linux学习环境:Ubuntu、Fedora等皆可;arm-linux-gnueabi-gcc 交叉编译工具;Linux 内核源码:Linux 4.4.xU-boot-2016.09 源代码备注如果您手头暂时没有 Linux 学习环境,也可以在 Windows 环境下安装 C-Free 学习。教程中的 C 语言示例程序在 C-Free 环境下面也能编译通过。当然在这里,还是建议您使用虚拟机安装一个 Linux 学习环境,一个良好的环境更有利于我们的学习,在安装过程有什么疑惑,可以加入QQ群(475504428),参与技术讨论。 ...

March 9, 2019 · 2 min · jiezi

每日一算法:百元百鸡

100元买100只,题目:公鸡5元一只、母鸡3元一只、小鸡1元三只求100元买一百只,各可以买几只三个变量:公鸡数量为 x 母鸡数量为 y 小鸡数量为 z满足条件:①:x+y+z=100 ②:5x+3y+z/3=100以上为计算题目得出方程/** * 公鸡5元一只、母鸡3元一只、小鸡1元三只 * 求100元买一百只,各可以买几只 * 三个变量:公鸡数量为 x 母鸡数量为 y 小鸡数量为 z * 满足条件:x+y+z=100 5x+3y+z/3=100 * 以上为计算题目得出方程 /void bj(int amount, int num) { int x, y, z = 0; // 公鸡数量 for (x = 0; x <= num; x++) { // 母鸡数量 for (y = 0; y <= num; y++) { // 小鸡数量 z = num - x - y; // 满足条件的 if (z % 3 == 0 && x * 5 + y * 3 + z / 3 == amount) { printf(“公鸡:%d,母鸡:%d,小鸡:%d\n”, x, y, z); } } }}int main(){ bj(100,100); return 0;}以上不是最优的办法,因为最外层循环了 num 次,第二层循环也循环了 num次,这里可以得到一部分优化void bj(int amount, int num) { int x, y, z = 0; // 公鸡数量 for (x = 0; x <= num / 5; x++) { // 母鸡数量 for (y = 0; y <= num / 3; y++) { // 小鸡数量 z = num - x - y; // 满足条件的 if (z % 3 == 0 && x * 5 + y * 3 + z / 3 == amount) { printf(“公鸡:%d,母鸡:%d,小鸡:%d\n”, x, y, z); } } }}int main() { bj(100, 100); return 0;}这样的优化我们不必需要两个循环都循环num次了,只要循环符合amount的最大数,这样可以避免过多不必要的循环。这样就是最优的解法了吗?当然不是,我们这里可以看到时间复杂度是:O(N2),那我们有没有办法优化到O(N)呢?我们来看看结果:公鸡:0,母鸡:25,小鸡:75公鸡:4,母鸡:18,小鸡:78公鸡:8,母鸡:11,小鸡:81公鸡:12,母鸡:4,小鸡:84我们来找一下规律,在这四条结果中,公鸡的规律是4的倍数,母鸡是7的递减,小鸡是3的递增。我们还记得在一开始的时候提到的方程组:①:x+y+z=100 ②:5x+3y+z/3=100上面两个方程组,有三个未知变量,为不定方程组令②×3-①得:7x+4y=100由 x+y+z = 100和5x + 3y + z/3 = 100可得7x+4y=100则y = 25 -(7/4)X再令x = 4k,则有y = 25 - 7k,继而z = 75 + 3k因为 0 =< z <= 100,所以k的可能取值是0,1,2,3void bj(int amount, int num) { int x, y, z = 0; for (int k = 1; k <= 3; k++) { x = 4k; y = 25-7k; z = 75+3k; printf(“公鸡:%d,母鸡:%d,小鸡:%d\n”, x, y, z); }}int main() { bj(100, 100); return 0;}以上的时间复杂度就可以为O(N) ...

March 9, 2019 · 2 min · jiezi

基于PWM调宽的呼吸灯算法

本文提供基于Texas Instruments 公司开发的Tiva C Series的系统板 ––—- TM4C123GH6PM ,以及DY - Tiva - PB v3.0 的拓展板实现呼吸灯算法。核心思路是通过循环调整亮灭的时间,总时间一定,使灭的时间与亮的时间成反比 int i = 0, j = 0, flag = 0; int m = 180, n = 180, t = 180, q = 180; //设置系统时钟为50MHz SysCtlClockSet(SYSCTL_SYSDIV_4 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_16MHZ); // 端口GPIO F使能,F0引脚解锁NMI功能 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF); // 使能 GPIO F模块 HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = GPIO_LOCK_KEY; // 开锁 PORT F HWREG(GPIO_PORTF_BASE + GPIO_O_CR) |= GPIO_PIN_0; // 解锁 F0 引脚 HWREG(GPIO_PORTF_BASE + GPIO_O_LOCK) = 0; // 重新上锁 // 设置GPIO方向 GPIOPinTypeGPIOOutput(GPIO_PORTF_BASE, GPIO_PIN_0); // PF0设置为输出 GPIOPinWrite(GPIO_PORTF_BASE,GPIO_PIN_0,1<<0); // 蓝LED灯初始态:灭 // 设置PA4为输出 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOA); // 使能 GPIO A模块 GPIOPinTypeGPIOOutput(GPIO_PORTA_BASE, GPIO_PIN_4); // PA4设置为输出 GPIOPinWrite(GPIO_PORTA_BASE,GPIO_PIN_4,1<<4); // 绿LED灯初始态:灭 // 设置PD6为输出 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD); // 使能 GPIO D模块 GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_6); // PD6设置为输出 GPIOPinWrite(GPIO_PORTD_BASE,GPIO_PIN_6,1<<6); // 红LED灯初始态:灭// while(1) // 死循环,main函数没有结束 { GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_0, 0 << 0); // 蓝色LED灯:亮 for(i = m; i > 0; i–) for(j = m; j > 0; j–); // 软件延时 GPIOPinWrite(GPIO_PORTF_BASE, GPIO_PIN_0, 1 << 0); // 蓝色LED灯:灭 for(i = q - m; i > 0; i–) for(j = q - m; j > 0; j–); // 软件延时 GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_4, 0 << 4); // 绿色LED灯:亮 for(i = m; i > 0; i–) for(j = m; j > 0; j–); // 软件延时 GPIOPinWrite(GPIO_PORTA_BASE, GPIO_PIN_4, 1 << 4); // 绿色LED灯:灭 for(i = n - m; i > 0; i–) for(j = n - m; j > 0; j–); // 软件延时 GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_6, 0 << 6); // 红色LED灯:亮 for(i = m; i > 0; i–) for(j = m; j > 0; j–); // 软件延时 GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_6, 1 << 6); // 红色LED灯:灭 for(i = t - m; i > 0; i–) for(j = t - m; j > 0; j–); // 软件延时 if((m == 0) && (flag == 0)) flag = 1; if(!flag) m–; else { if(m < n) m++; else flag = 0; } } ...

March 6, 2019 · 2 min · jiezi

理解deno-基础篇

deno介绍deno是一个基于v8、rust和Tokio的Javascript/Typescript的安全运行时。它在内部嵌入了一个typescript的编译器。可以将typescript编译成js然后运行在v8上,并通过c++ libdeno实现js与rust的通信交互,当然deno也可以直接运行Javascript代码。deno安装linux/maccurl -fsSL https://deno.land/x/install/install.sh | shwindowsiwr https://deno.land/x/install/install.ps1 | iex详细的细节请参考官网安装介绍deno的简单例子计算斐波那契数列公式摘抄自维基百科斐波那契数列run javascripttest.jsfunction fibo(n) { if (n === 0) return 0; let fn = 0, nextFn = 1; let tmp_fn = 0, tmp_nextFn = 0; for (var i = 0; i < n; i++) { tmp_fn = nextFn; tmp_nextFn = nextFn + fn; fn = tmp_fn; nextFn = tmp_nextFn; } return tmp_fn;}console.log(fibo(13));deno ./test.jsrun typescripttest.tsfunction fibo(n: number) { if (n === 0) return 0; let fn: number = 0, nextFn: number = 1; let tmp_fn: number = 0, tmp_nextFn: number = 1; for (let i: number = 0; i < n; i++) { tmp_fn = nextFn; tmp_nextFn = nextFn + fn; fn = tmp_fn; nextFn = tmp_nextFn; } return tmp_fn;}console.log(fibo(13));deno ./test.tsdeno内部结构下图1是deno的部分文件截图图1 deno文件的部分截图上图中圈出来的三个文件夹分别是jslibdenosrc分别对应deno的api层、中间层、和实现层,其中js中主要是typescript的代码,包含typescript的编译器和deno暴露给用户的api。libdeno中主要是c++代码,用来加载v8实例,实现typescript和rust的通信。src文件中主要是rust的代码,是deno功能的具体实现。例如用户使用File实例的write方法来写文件,实际上是api层(typescript)通过中间层(libdeno)将数据传输给实现层(rust),最终写文件操作由rust去完成。deno结合了Typescript/Javascript的易用性和rust的系统语言能力。下图2可以清晰的表示js和rust之间的逻辑关系。图来自于官网,图2 deno的架构图预告~~~接下来还会有两篇文章分析deno的内部原理~~~ ...

March 6, 2019 · 1 min · jiezi

C语言下实现的String库

C语言的StringC语言作为一门古老的高级语言,对于字符串的支持十分的薄弱。入门时我们就知道我们使用数组来包含一串的ASCII字符来作为字符串的实现,如char arr[] = “hello world!";这样基于长度固定的数组的实现方式就导致了C的字符串的长度是不可变的,但是arr[]的内容却是可变的。这样的设计导致很多时候我们对字符串的处理十分的麻烦与危险,像我之前写的哈夫曼编码解码的时候,为了盛放解码后的结果,我不得不创建一个非常大的静态数组或者动态分配内存来放置函数产生的长度不定的字符串。相较于其后辈(如Python/Java,C++基本兼容C的语法,尽管C++实现了自己的string类),C在很多方面也是比较异类的,比如C使用’\0’来标志字符串的结束,因而len(arr)这样的操作的复杂度就达到了O(n),这是一个比较大的开销,而Pascal/Python等的实现都可以做到O(1),同时,由于char类型本身就是最短的整型再加上C语言的弱类型的类型系统,‘a’- 32也是完全有效的语法,而在Python中这会引发TypeError. 这些问题在C语言诞生的年代不是大问题,毕竟当时没有那么多字符串的处理需求,而且C主要的应用场景也比较偏底层。而现在,一些选择C实现的程序需要频繁的处理字符串(如 Redis,需要频繁的处理键值对),为了应对这种场景,很多很有意思的自己的String实现都被提了出来。在这里我主要是介绍ccan的xstring和sds的一些实现的思路。xstring/** * struct xstring - string metadata * @str: pointer to buf * @len: current length of buf contents * @cap: maximum capacity of buf * @truncated: -1 indicates truncation */typedef struct xstring { char *str; size_t len; size_t cap; int truncated;} xstring;xstring xstrNew(const size_t size){ char str; xstring x; if (size < 1) { errno = EINVAL; return NULL; } str = malloc(size);//mark 1 if (!str) return NULL; x = malloc(sizeof(struct xstring));//mark 2 if (!x) { free(str); return NULL; } xstrInit(x, str, size, 0); return x;}透过xstring结构体与xstrNew(const size_t size)这个创建新的xstring的函数,ccan的这个实现的思路就比较清晰了,xstring结构体本身占据内存,但是并不存储字符串,字符串在mark 1被分配存储空间,而结构体在mark 2被分配内存。PS:在刚刚学习使用C来实现数据结构的时候,我很疑惑为何不能直接struct xstring newStruct(){ struct xstring s; return &s;}直到后来才逐渐明白了栈上的变量与动态分配的变量的微妙的区别,s在这个函数返回后就已经被销毁了,传出的这个地址是无效的,而对他的引用很可能会导致段错误(segment fault),操作系统,编译原理等课真的会让自己对于程序设计语言获得更深的理解。而且这种写法当时很有吸引力,毕竟不用malloc,不用强制类型转换。这种野指针是很多很难修正的错误的来源,有兴趣的同学可以去学习一下Rust语言的所有权系统,很多的概念很有意思。| xstring | -> | str |可以看出xstring的实现中内存是分为两个部分的。Note: xstring只需要编译器支持C89/90。sdsredis sds(simple dynamic string)是Redis对于str的实现,在这里有官方对于sds实现的一些技巧的介绍,在这里我会将SDS实现的主要的细节介绍以下。// sds 类型typedef char sds;// sdshdr 结构struct sdshdr { // buf 已占用长度 int len; // buf 剩余可用长度 int free; // 实际保存字符串数据的地方 // 利用c99(C99 specification 6.7.2.1.16)中引入的 flexible array member,通过buf来引用sdshdr后面的地址, // 详情google “flexible array member” char buf[];};和上面的实现不太一样的是sds只存储存储的字符串长度以及剩余长度,但是最引人瞩目的无疑是最后的那一个数组声明:char buf[];结构体中竟然没有声明数组的大小,这样好像与我们对于数组一贯的印象不符,但是这是合法的特性,叫做柔性数组。具体的语法细节我不再介绍,但是注意以下几点sizeof(struct sdshdr) == sizeof(len) + sizeof(buf),在x86_64上典型值应该为8个字节(4 + 4),这说明buf[]没有实际占据空间,一个64位系统下的指针就要8个字节。上面的写法是C99 only的,这个特性应该来自于以下这种写法,struct header { size_t len; unsigned char data[1];};这种写法下data就是一个 unsigned char型的指针,可以通过它用来访问存储的字符串。//在分配内存的时候,结构体中存储了一个字符,其他的(n-1)个空间在//紧随结构体结束地址的地方// | struct (char) | (n - 1) char |ptr = malloc(sizeof(struct header) + (n-1));对比sds中的实现,sds中不存储任何一个数据,只有一个不占据内存空间的标记代表,所有的数据都存储在结构体所占空间后面| struct | str |我们来看这有什么用: / * 返回 sds buf 的已占用长度 / static inline size_t sdslen(const sds s) { struct sdshdr sh = (void)(s-(sizeof(struct sdshdr))); return sh->len; } / * 返回 sds buf 的可用长度 / static inline size_t sdsavail(const sds s) { struct sdshdr sh = (void)(s-(sizeof(struct sdshdr))); return sh->free; } / * 创建一个指定长度的 sds * 如果给定了初始化值 init 的话,那么将 init 复制到 sds 的 buf 当中 * * T = O(N) */ sds sdsnewlen(const void init, size_t initlen) { struct sdshdr sh; // 有 init ? // O(N) if (init) { sh = zmalloc(sizeof(struct sdshdr)+initlen+1); } else { sh = zcalloc(sizeof(struct sdshdr)+initlen+1); } // 内存不足,分配失败 if (sh == NULL) return NULL; sh->len = initlen; sh->free = 0; // 如果给定了 init 且 initlen 不为 0 的话 // 那么将 init 的内容复制至 sds buf // O(N) if (initlen && init) memcpy(sh->buf, init, initlen); // 加上终结符 sh->buf[initlen] = ‘\0’; // 返回 buf 而不是整个 sdshdr return (char)sh->buf; } 我们创建一个新的sds的时候,分配sizeof(struct sdshdr) + len + 1大小的空间,len代表不包含结束符号在内的容量,最后我们返回的是字符串开始的地址,这个返回的地址可以直接作为一般的字符串被其他库函数等使用,即Redis所说的二进制兼容的(因为其内部也使用'0’结尾)。同时结构体的地址可以通过用字符串的地址减去结构体的大小得到struct sdshdr sh = (void)(s-(sizeof(struct sdshdr)));这样一来sds可以在常数时间内获得字符串的长度。#include <stdio.h>#include “./src/simple_dynamic_str.h"int main() { sds s = sdsnew(“Hello World! K&R”); printf("%s\n”, s); printf("%zu %zu\n”, sdslen(s), sdsavail(s)); printf("%c",s[0]); return 0;}结果:Hello World! K&R16 0H这种通过指针的计算获得结构体的地址的方式还是比较少见的技巧,我也只是在Linux内核的task_struct结构体中见识过类似的技巧,当然那个更复杂。这种操作是很危险的,但是C数组在这方面其实也没有好多少(并没有多出数组越界检查等),不是吗?在字符串较短时,结构体占据放入空间是比较可观的,更新版本的Redis优化了不同长度的字符串结构体的定义。/ Note: sdshdr5 is never used, we just access the flags byte directly. * However is here to document the layout of type 5 SDS strings. /struct attribute ((packed)) sdshdr5 { unsigned char flags; / 3 lsb of type, and 5 msb of string length / char buf[];};struct attribute ((packed)) sdshdr8 { uint8_t len; / used / uint8_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[];};struct attribute ((packed)) sdshdr16 { uint16_t len; / used / uint16_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[];};struct attribute ((packed)) sdshdr32 { uint32_t len; / used / uint32_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits / char buf[];};struct attribute ((packed)) sdshdr64 { uint64_t len; / used / uint64_t alloc; / excluding the header and null terminator / unsigned char flags; / 3 lsb of type, 5 unused bits */ char buf[];};总结这篇文章中有些技巧还是有些难度的,像sds我也是花了一些时间才弄明白其原理,这里的两种实现我个人更偏爱第二种,但是这毕竟是二等公民,没有语言级别的支持是硬伤。所以如果真的需要大量处理字符串,特别是非纯ASCII码,左转Java/Python etc.reference:redis sds(simple dynamic string)ccan xstringRedis设计与实现https://stackoverflow.com/que…https://redis.io/topics/inter… ...

March 1, 2019 · 3 min · jiezi

Head First JNA

问题描述虚拟化项目,需要用到Java调用原生代码的技术,我们使用的是开源库JNA(Java Native Access)。Native(C/C++)代码,编译生成动态链接库Dynamic-link library。在Windows下常见的.dll文件。这是我们项目中用到的动态链接库。而在unix环境下,为.so文件。这是百度地图的动态链接库。与动态链接库配套的,会有相应的头文件,来声明动态链接库中对外暴露的方法。百度地图是直接封装好,给了.so,但是不给头文件,直接把写好的jar包给你,直接调用就行。之前也是用过百度地图的SDK,现在自己手写代码调用动态链接库才明白,原来之前用的都是别人封装好的,如今自己参照头文件手写,感觉理解还是深刻了不少。入门待解决的问题我们使用JNA,主要是去调用动态链接库中已经实现的方法,所以要解决的问题就是:如何在Java代码中调用动态链接库的方法?打开头文件,这个方法要求传输的数据是指针,而Java是没有指针的,另一个问题:Java数据类型与C/C++的数据类型如何映射?方法映射打开JNA的官方README,点击Getting Started。直接看代码,里面的sample,入门足够了。定义接口,继承Library。定义该接口的一个实例,加载动态链接库。在接口中声明方法,该方法需要与原生代码方法声明一致。然后该方法就映射到了原生的方法,我们只需调用该接口中的方法,即可调用到原生的对应方法。// This is the standard, stable way of mapping, which supports extensive// customization and mapping of Java to native types.public interface CLibrary extends Library { CLibrary INSTANCE = (CLibrary) Native.load((Platform.isWindows() ? “msvcrt” : “c”), CLibrary.class); void printf(String format, Object… args);}类型映射默认类型映射这是官方README中给的类型映射。学习与实践的区别,看着这个表格感觉挺简单的,实际用起来真难。结构体映射结构体PSA_HOST:typedef struct { char name[33]; DWORD context;} PSA_HOST;映射类HostStruct:编写类HostStruct,继承Structure,表示这个一个结构体。声明字段name与context,并且设置访问属性为public。重写getFieldOrder方法,表示本类中各字段以何顺序映射原生结构体。/** * @author zhangxishuo on 2019-02-16 * 结构体 PSA_HOST /public class HostStruct extends Structure { public byte[] name = new byte[33]; public int context; @Override protected List<String> getFieldOrder() { return Arrays.asList(“name”, “context”); }}注意const char 才能映射为String类型。而char 只能映射为byte数组,然后使用Native.toString()方法将byte数组转换为String。方法映射typedef PSA_STATUS (LPFN_PSA_ShutdownHost)( PSA_LOGON_HANDLE handle, IN PSA_HOST psa_host );参数中需要PSA_HOST结构体的指针。参考了好多篇文章,最常用的就是下面这种写法。写静态内部类,内部类实现ByReference与ByValue接口,分别表示映射指针,与映射值。/ * @author zhangxishuo on 2019-02-16 * 结构体 PSA_HOST /public class HostStruct extends Structure { / * 结构体指针 / public static class ByReference extends HostStruct implements Structure.ByReference { } / * 结构体具体的值 / public static class ByValue extends HostStruct implements Structure.ByValue { } public byte[] name = new byte[33]; public int context; @Override protected List<String> getFieldOrder() { return Arrays.asList(“name”, “context”); }}映射/ * 关闭计算机 * @param pointerByReference 认证pointer * @param host 主机指针 * @return 参见枚举类PsaStatus /NativeLong _PSA_ShutdownHost(PointerByReference pointerByReference, HostStruct.ByReference host);复杂类型打起精神,重点来了!开发过程中,有这样一种复杂的数据结构,嵌套关系比较复杂。typedef struct { union { DWORD flags; struct { DWORD rev:23; DWORD copy_status:3; DWORD timeout:1; DWORD disconnect:1; DWORD rev1:1; DWORD os_logoned:1; DWORD logoned:1; DWORD online:1; }; };} PSA_HOST_STATUS_FLAGS;知识不用就忘,谁还记得C语言里的联合是啥?Too youngstruct { DWORD rev:23; DWORD copy_status:3; DWORD timeout:1; DWORD disconnect:1; DWORD rev1:1; DWORD os_logoned:1; DWORD logoned:1; DWORD online:1;};DWORD就是int,先映射里面的结构体。把这些属性一写,然后再重写getFieldOrder方法。/ * @author zhangxishuo on 2019-02-26 * 计算机状态结构体 /public class HostStatusStruct extends Structure { /* * 结构体指针 / public static class ByReference extends HostStatusStruct implements Structure.ByReference { } /* * 结构体具体的值 / public static class ByValue extends HostStatusStruct implements Structure.ByValue { } public int rev; public int copy_status; public int timeout; public int disconnect; public int rev1; public int os_logoned; public int logoned; public int online; @Override protected List<String> getFieldOrder() { return Arrays.asList(“rev”, “copy_status”, “timeout”, “disconnect”, “rev1”, “os_logoned”, “logoned”, “online”); }}union { DWORD flags; struct { DWORD rev:23; DWORD copy_status:3; DWORD timeout:1; DWORD disconnect:1; DWORD rev1:1; DWORD os_logoned:1; DWORD logoned:1; DWORD online:1; };};然后再映射联合,编写一个类继承Union,该类即映射到联合。/* * 联合 /public static class UNION extends Union { public int flags; public HostStatusStruct hostStatusStruct;}最后映射最外层的PSA_HOST_STATUS_FLAGS。/* * @author zhangxishuo on 2019-02-26 * 结构体 PSA_HOST_STATUS_FLAGS /public class HostStatusFlagsStruct extends Structure { /* * 联合 / public static class UNION extends Union { public int flags; public HostStatusStruct hostStatusStruct; } /* * 结构体指针 / public static class ByReference extends HostStatusFlagsStruct implements Structure.ByReference { } /* * 结构体具体的值 / public static class ByValue extends HostStatusFlagsStruct implements Structure.ByValue { } public UNION union; @Override protected List<String> getFieldOrder() { return Collections.singletonList(“union”); }}看上去好像没什么毛病,一切都这么简单吗?当然不是。-1073741824一调用,就炸了。看API文档的声明,flags应该是没什么具体含义的,而结构体中应该是我们想要获取的信息。那为什么flags有数,而结构体中却没有值呢?联合struct TEST_STRUCT { int a, int b};union TEST_UNION { int a, int b};声明映射类型重写read方法,当读取数据时,设置联合的类型为结构体类型。@Overridepublic void read() { super.read(); union.setType(HostStatusStruct.class); union.read();}怪事当时这个问题把我愁坏了,捯饬了一整天才学明白。数字一直是这个数字:-1073741824,这个数字是不是有什么问题?把-1073741824转换为32机的二进制表示:1100 0000 0000 0000 0000 0000 0000 00001073741824是2的30次方。问题问题还是出在这个上:struct { DWORD rev:23; DWORD copy_status:3; DWORD timeout:1; DWORD disconnect:1; DWORD rev1:1; DWORD os_logoned:1; DWORD logoned:1; DWORD online:1;};虽然DWORD就映射为int,但这里不是简单的映射:rev:23表示rev占32位。copy_status:3表示copy_status占3位。timeout:1表示timeout占1位。内存是倒着存的,所以数据应该是这样。映射原理知道了,那怎么映射呢?怎么映射一个一位的内容呢?StackOverflow上一老哥给出了解决方案,先写个int把所有的都映射过来,然后我想要第几位再从里面扒。/* * @author zhangxishuo on 2019-02-26 * 计算机状态结构体 /public class HostStatusStruct extends Structure { /* * 结构体指针 / public static class ByReference extends HostStatusStruct implements Structure.ByReference { } /* * 结构体具体的值 */ public static class ByValue extends HostStatusStruct implements Structure.ByValue { } public int value; public int getRev() { return value & 0x3FFFFF; } public int getCopyStatus() { return (value >> 23) & 0x3; } public int getTimeout() { return (value >> 26) & 0x1; } public int getDisconnect() { return (value >> 27) & 0x1; } public int getRev1() { return (value >> 28) & 0x1; } public int getOsLogoned() { return (value >> 29) & 0x1; } public int getLogoned() { return (value >> 30) & 0x1; } public int getOnline() { return (value >> 31) & 0x1; } @Override protected List<String> getFieldOrder() { return Collections.singletonList(“value”); }}至此,功能完成。总结又过去了忙碌的一周,很高兴我们的新项目已经完成大半。感谢潘佳琦与李宜衡在本项目中的支持,第一次用Angular,好多地方我也不懂,我先学着,然后设计一套架构付诸实践,潘佳琦与李宜衡也都能遵从我制定的规范。起初,我也提出了许多错误的规范,但当我用着用着发现原来那套不行的时候,及时改正,修改架构再重新设计,潘佳琦与李宜衡也在前台经历了大约三次的代码重构。前台架构变更多次,感觉最后的设计还让人满意,也能让他人快速理解这种设计理念。争取下一个项目,不使用ng-alain,自己从头到尾搭建一个项目骨架。最后表扬一下潘佳琦,上周基本我有一半的时间都在上课,我能做的就是前一天晚上把任务建好,然后写一些基础代码或示例代码,然后给潘佳琦讲,再让他去写。潘佳琦效率还是很高的,我记得周一的时候建了一堆任务,我想怎么着也得写两天吧,当我上课回来,发现“当当当”,潘佳琦都给写完了,代码也十分的规范。对小组员的开发效率在心中也有了一个重新的定位。 ...

March 1, 2019 · 3 min · jiezi

进制转换的那些事儿

进制转换的那些事儿进制转换是一种较为特殊的数位拆解以下解释部分来源:知乎网友进制这事儿,说到底就是位值原理,即:同一个数字,放在不同的数位上,代表不同大小的“量”。例如:十进制中,百位上的1表示100,十位上的1表示10。任何进制中,每个数都可以按位权展开成各个数位上的数字乘以对应数位的位权,再相加的形式,如: 十进制的123=1×100+2×10+3×1 十进制的9876=9×1000+8×100+7×10+6×1问:为啥相应的数位是1000、100、10、1?为啥不是4、3、2、1?答:十进制,满十进一,再满十再进一,因此要想进到第三位,得有10×10;第4位得有10×10×10这样我们就知道了:对10进制,从低位到高位,依次要乘以10^0,10^1,10^2,10^3……,也就是1、10、100、1000对2进制,从低位到高位,依次要乘以2^0,2^1,2^2,2^3……,也就是1、2、4、8……总之,n进制k转换成 m进制t,只需先将n进制k转换成十进制q,再将十进制q转换成m进制t题目描述求任意两个不同进制非负整数的转换(2进制~16进制),所给整数在long所能表达的范围之内。不同进制的表示符号为(0,1,…,9,a,b,…,f)或者(0,1,…,9,A,B,…,F)。输入输入只有一行,包含三个整数a,n,b。a表示其后的n 是a进制整数,b表示欲将a进制整数n转换成b进制整数。a,b是十进制整数,2 =< a,b <= 16。输出可能有多组测试数据,对于每组数据,输出包含一行,该行有一个整数为转换后的b进制数。输出时字母符号全部用大写表示,即(0,1,…,9,A,B,…,F)。样例输入15 Aab3 7样例输出210306代码块int main() { int a, b; char n[40]; while (scanf("%d%s%d", &a, n, &b)!=EOF) { int size1 = strlen(n); int res=0; for (int i = size1-1; i >= 0; i–) { int x; if (n[i] >= ‘0’ && n[i] <= ‘9’) { x = n[i] - ‘0’; } else if (n[i] >= ‘a’ &&n[i] <= ‘z’) { x = n[i] - ‘a’+10; } else { x= n[i] - ‘A’ + 10; } res += x * pow(a, size1 - i - 1); } char ans[40]; int size = 0; while (res != 0) { int t = res%b; if (t < 10) { ans[size++] = t + ‘0’; } else { ans[size++] = t - 10 + ‘A’; } res /= b; } for (int i = size-1; i>=0 ; i–) { printf("%c", ans[i]); } printf("\n"); } return 0;}总结1.字符转换,根据ACSII码,进行数字与字符之间的转换。 if (n[i] >= ‘0’ && n[i] <= ‘9’) { x = n[i] - ‘0’; } else if (n[i] >= ‘a’ &&n[i] <= ‘z’) { x = n[i] - ‘a’+10; } else { x= n[i] - ‘A’ + 10; } ...

February 28, 2019 · 1 min · jiezi

【Linux系统编程】快速查找errno错误码信息

我们都知道,errno整型变量被普遍应用于*NIX C的异常处理中,其记录了最近一次的错误码。通过判断错误码的值,以此执行不同的错误处理,这是C语言典型的异常处理方式。其错误名称,比如EAGAIN、EWOULDBLOCK等,都通过宏定义,头文件是errno.h;错误码对应的描述,可以通过strerror输出。如果我们想知道错误名称对应的错误码的值,只需要简单的print("%d",EAGAIN);就能知道;如果想知道错误码的描述,调用strerror即可。但毕竟要写程序,相对不方便,那有没有现成的工具帮我们做到这些呢?Linux有一款errno命令行程序可以很方便的解决以上的问题,而且还能提供更丰富的功能。安装在Debian可通过apt-get install moreutils安装,这个软件包里包含很多的命令程序,有时间可以挖掘一下。功能说明errno程序选项很少,通过man errno一屏就可展开。下面说主要功能:通过错误名称查错误码和错误描述$ errno EWOULDBLOCKEWOULDBLOCK 11 Resource temporarily unavailable通过错误码查错误名称和错误描述$ errno 11EAGAIN 11 Resource temporarily unavailable列举所有errno变量所有错误情况使用errno -l 或 errno -ls通过错误描述里的关键字(大小写不敏感)查对应的错误情况$ errno -s supportEPROTONOSUPPORT 93 Protocol not supportedESOCKTNOSUPPORT 94 Socket type not supportedEOPNOTSUPP 95 Operation not supportedEPFNOSUPPORT 96 Protocol family not supportedEAFNOSUPPORT 97 Address family not supported by protocolENOTSUP 95 Operation not supported 请关注我的公众号哦。

February 28, 2019 · 1 min · jiezi

利器|谷歌开源模糊测试工具 ClusterFuzz 应用尝鲜从 0 到 1

本文发表于 TesterHome 社区,作者为资深测试开发工程师恒捷,原文标题为《谷歌开源模糊测试工具 ClusterFuzz 尝鲜记录 (进行中)》,原文链接:https://testerhome.com/topics…背景模糊测试,是指用随机坏数据(也称做 fuzz)攻击一个程序,然后等着观察哪里遭到了破坏。(出自 模糊测试)。一直以来都有不少的模糊测试工具,但大多只集中在数据生成,执行和异常检测依赖人工,未有比较完整的方案。早在八年前,google 内部就在建设和使用模糊测试的工具来测试其内部的应用,而在两年前, google 推出了 OSS-Fuzz 服务,用于给开源项目的进行免费的模糊测试服务,可自动在新版本代码提交后自动完成 测试->异常检测->issue登记->老版本issue回归及自动关闭 的功能。背后使用的就是 ClusterFuzz 技术。流程图如下:而在过年前,google 开源了 ClusterFuzz ,并解决了原有 ClusterFuzz 必须依赖 Google Cloud 提供的服务这个问题,提供了本地运行的解决方案。根据官方介绍,它具备如下功能:高度可扩展,谷歌的内部实例运行在超过 25000 台机器上准确的去副本化(Accurate deduplication)问题跟踪器的全自动错误归档和关闭最小化测试用例通过二分法回归查找提供分析 fuzzer 性能和崩溃率的统计信息(不支持本地部署)易于使用的 Web 界面,用于管理和查看崩溃支持引导模糊(例如 libFuzzer 和 AFL)和黑盒模糊测试其大致执行流程如下:当然,方案并不完美,如模糊数据统计、崩溃数据统计等功能由于依赖 google cloud 强大的数据处理能力,本地运行时是用不了的。官方说的总是美好的,现实是否这么完美呢?曾有人说,实践是检验真理的唯一标准,为了更好地了解这个工具,当然就要本地跑个 demo 玩下啦。本地搭建及运行要获得 ClusterFuzz 的完整功能,需要连接 Google Cloud Platform。但结合国情,我们更期望了解它纯本地运行能做到什么,因此这次尝鲜主要尝试纯本地运行。注意:虽然运行可以脱离 Google Cloud Platform ,但部分安装时用到的工具需要到 Google 站点下载,所以,你懂得。以下步骤均是在 macOS 10.14 上进行。环境搭建1、下载源码git clone https://github.com/google/clusterfuzzcd clusterfuzz2、安装 google cloud sdk进入 https://cloud.google.com/sdk/ ,按照引导安装 sdk 并配置好环境变量(mac 下可以直接用解压后的 install.sh 脚本一键安装),确认命令行可调用 gcloud 命令$ gcloud -vGoogle Cloud SDK 226.0.0bq 2.0.38core 2018.11.16gsutil 4.343、安装 python 和 go 运行环境。特别注意:如果你使用的是 macOS 或者 Ubuntu、Debain,直接执行第4步即可,脚本里会自动安装 Python 和 gopython 要求 2.7.10 以上,但不能是 python 3。在 mac 上可以直接运行 brew install python@2 安装。go 未要求版本,在 mac 上可以直接运行 brew install go 安装。我用的是 go1.11.5 darwin/amd644、安装其他依赖针对Ubuntu (14.04, 16.04, 17.10, 18.04, 18.10)Debian 8 (jessie) or laterRecent versions of macOS with homebrew (experimental)几个系统,官方已经内置了安装依赖的脚本,直接运行即可:local/install_deps.bash执行完毕,会出现Installation succeeded!Please load virtualenv environment by running ‘source ENV/bin/activate’.的提示。坑一,官方的脚本里第一行用了 -ex 参数,会导致运行脚本时如果有命令执行出错(如 brew install 时有些应用本地已经安装过,但非最新版本),直接退出程序。可以通过 sed-i’‘’s/bash -ex/bash -x/’local/install_deps* 命令直接去掉 -e 参数。已经给官方提了 issue 。坑二,官方脚本里使用 python butler.py bootstrap 初始化环境时,会自动去 google 站点下载 chromedriver 相关的文件。全局搜索了下源代码,只有跑单测的时候有用到 chromedriver ,所以可以直接注释掉这个函数:diff –git a/src/local/butler/common.py b/src/local/butler/common.pyindex 94b17b3..3e9de99 100644— a/src/local/butler/common.py+++ b/src/local/butler/common.py@@ -275,7 +275,7 @@ def install_dependencies(platform_name=None):_remove_invalid_files()execute(‘bower install –allow-root’)- _install_chromedriver()+ #_install_chromedriver()def symlink(src, target):坑三,运行时会报错 Analysisof target’//local:create_gopath’failed;build aborted:nosuchpackage’@org_golang_google_api//iterator’:failed to fetch org_golang_google_api:2019/02/1901:15:41unrecognizedimportpath"google.golang.org/api"这是在运行 bazel 构建 go 环境的时候报错了,原因是 @org_golang_x_tools、@com_google_cloud_go、@org_golang_google_api 这几个第三方依赖网络原因获取不到。尝试一:使用代理因为 go 获取依赖有可能用 http ,也有可能用 git ,所以保险起见全部都配好代理:export HTTP_PROXY=http://112.126.81.122:6$(date +%m%d)export HTTPS_PROXY=${HTTP_PROXY}git config –global https.proxy ${HTTP_PROXY}git config –global http.proxy ${HTTP_PROXY}可惜的是,配置完了还是不行,bazel 构建时提示 fatal:unable to access’https://code.googlesource.com/google-api-go-client/':LibreSSLSSL_connect:SSL_ERROR_SYSCALLinconnection to code.googlesource.com:443,此路不通。尝试二:修改运行环境,改为在网络本身就没问题的地方运行嗯,哪里有这样的环境呢?一个是自己买云主机,另一个就是考虑用 docker hub 提供的构建环境了。看了下后面的使用步骤,也没有需要在源码目录做操作的部分,就选择 docker 吧。动手 fork 了官方仓库,开始了漫长的尝试:https://github.com/chenhengji…2.23 更新:docker 镜像已成功打包,基于 ubuntu 16.04 系统。镜像中已运行完毕本文中的第1-4步(除了坑2中的注释 chromedriver ),装好了所有依赖。镜像地址:https://hub.docker.com/r/chen…可通过 docker run-it–name clusterfuzz-p9000:9000-p41089:41089-p9001:9001-p9002:9002chenhengjie123/clusterfuzz_local 进入镜像运行环境,进入后续的步骤。clusterfuzz 的源代码存放在镜像的 /clusterfuzz 目录。5、切换到 python 的 virtualenv$ source ENV/bin/activate校验是否一切就绪$ python butler.py –helppython butler.py –helpDEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won’t be maintained after that date. A future version of pip will drop support for Python 2.7.usage: butler.py [-h]{bootstrap,py_unittest,go_unittest,js_unittest,format,lint,package,deploy,run_server,run,run_bot,remote,clean_indexes,generate_datastore_models,create_config}…运行本地实例本地实例包含2个部分,一个是管理各个执行机器人的服务端,另一个是执行机器人。启动本地服务首次运行,添加 –bootstrap 进行各个数据的初始化。同时个人推荐加上 –skip-install-deps 跳过依赖安装(前面步骤已经装过了,不需要重复安装)$ python butler.py run_server –bootstrap –skip-install-deps非首次运行,务必去掉 –bootstrap 参数。坑四:启动时会到 https://www.googleapis.com/discovery/v1/apis/pubsub/v1/rest 获取一些信息,如果此时网络连不通,会报错报错信息:Created symlink: source: /clusterfuzz/local/storage/local_gcs, target /clusterfuzz/src/appengine/local_gcs.Traceback (most recent call last):File “butler.py”, line 282, in <module>main()File “butler.py”, line 256, in maincommand.execute(args)File “src/local/butler/run_server.py”, line 162, in executetest_utils.setup_pubsub(constants.TEST_APP_ID)File “/clusterfuzz/src/python/tests/test_libs/test_utils.py”, line 308, in setup_pubsub_create_pubsub_topic(client, project, queue[’name’])File “/clusterfuzz/src/python/tests/test_libs/test_utils.py”, line 284, in _create_pubsub_topicif client.get_topic(full_name):File “/clusterfuzz/src/python/google_cloud_utils/pubsub.py”, line 192, in get_topicrequest = self._api_client().projects().topics().get(topic=name)File “/clusterfuzz/src/python/base/retry.py”, line 88, in _wrapperresult = func(args, **kwargs)File “/clusterfuzz/src/python/google_cloud_utils/pubsub.py”, line 89, in _api_clientdiscovery.DISCOVERY_URI.format(api=‘pubsub’, apiVersion=‘v1’))File “/clusterfuzz/src/third_party/httplib2/init.py”, line 1694, in request(response, content) = self._request(conn, authority, uri, request_uri, method, body, headers, redirections, cachekey)File “/clusterfuzz/src/third_party/httplib2/init.py”, line 1434, in _request(response, content) = self._conn_request(conn, request_uri, method, body, headers)File “/clusterfuzz/src/third_party/httplib2/init.py”, line 1390, in _conn_requestresponse = conn.getresponse()File “/usr/lib/python2.7/httplib.py”, line 1123, in getresponseraise ResponseNotReady()httplib.ResponseNotReady解决猜想:看了下这个页面,实际上是获取 api 文档。理论上只要把这个 api 文档事先下载好并放到资源文件中,然后把这个从网络获取文档的步骤改为读取资源文件即可。晚些尝试下。由于时间关系,暂时先想办法让网络能访问 google 先绕过。启动到末尾,会出现如下日志:| INFO 2019-02-23 06:25:34,648 api_server.py:265] Starting gRPC API server at: http://localhost:39957| INFO 2019-02-23 06:25:34,877 dispatcher.py:256] Starting module “default” running at: http://localhost:9000| INFO 2019-02-23 06:25:35,021 dispatcher.py:256] Starting module “cron-service” running at: http://localhost:9001| INFO 2019-02-23 06:25:35,023 admin_server.py:150] Starting admin server at: http://localhost:9002表明已经启动完毕。可以通过打开 http://localhost:9002/ 打开管理员界面。坑五:内部监听地址都是 localhost ,意味着在 docker 容器内部时,即使用 -p 暴露了端口也访问不了解决猜想:把源码中 localhost 都替换为 0.0.0.0 ,即监听所有地址,应该可以解决。目前还在修改中。后续部分翻译自官方文档,还没亲测,大家可以先看看了解。 ====== 官方文档翻译分割线 ======启动执行机器人官方命令:python butler.py run_bot –name my-bot /path/to/my-bot其中 my-bot 可以替换为自己喜欢的名称。我改成了 fuzzing-bot$ python butler.py run_bot –name fuzzing-bot cwd/fuzzing-bot执行成功后,可在前一步的管理员界面看到机器人状态。可通过tail -f cwd/fuzzing-bot/bot.log查看机器人实时日志输出。开始测试官方给了一个例子,寻找 OpenSSL 的心脏滴血内存溢出漏洞。下面按照给出的步骤执行。编译一个包含这个漏洞和已经带有 fuzz 插桩的 OpenSSL# 下载并解压包含这个漏洞的 OpenSSL :curl -O https://www.openssl.org/source/openssl-1.0.1f.tar.gztar xf openssl-1.0.1f.tar.gz# 使用 AScan 和 fuzzer 插桩编译 OpenSSL:cd openssl-1.0.1f/./config# 注意:$CC 必须指向 clang 二进制文件。简单地说,按照这个命令来写就对了make CC="$CC -g -fsanitize=address,fuzzer-no-link"cd ..# 下载 fuzz target 和它的数据依赖:curl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/handshake-fuzzer.cccurl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.keycurl -O https://raw.githubusercontent.com/google/clusterfuzz/master/docs/setting-up-fuzzing/heartbleed/server.pem# 编译可用于 ClusterFuzz 的 OpenSSL fuzz target ($CXX 需要指向一个 clang++ 二进制文件):$CXX -g handshake-fuzzer.cc -fsanitize=address,fuzzer openssl-1.0.1f/libssl.a \openssl-1.0.1f/libcrypto.a -std=c++17 -Iopenssl-1.0.1f/include/ -lstdc++fs -ldl -lstdc++ -o handshake-fuzzerzip openssl-fuzzer-build.zip handshake-fuzzer server.key server.pem上传 fuzzer 到 ClusterFuzz1、进入 Jobs 页面,点击 【ADD NEW JOB】按钮 2、job 的各个输入框填写以下内容:输入框名称内容Namelibfuzzerasanlinux_opensslPlatformLINUXTemplateslibfuzzer engine_asanEnvironment StringCORPUS_PRUNE = True3、把上一步打包的 openssl-fuzzer-build.zip 文件上传到 “Custom Build” 字段 4、点击 【ADD】 按钮,完成添加 5、点击【Select/modify jobs】,勾选 “libfuzzerasanlinux_openssl” ,然后点击【SUBMIT】 按钮执行及查看结果通过查看本地的机器人执行日志,可以发现 fuzz libFuzzer libfuzzer_asan_linux_openssl 这个字符串,代表目前 fuzz 测试已经在进行中了。稍等一会,会在日志中发现一个堆栈信息和 AddressSanitizer:heap-buffer-overflow 出现在日志中。再稍等一会,可以在 <> 页面看到一个标题为 “Heap-buffer-overflow READ{}” 的测试用例,这个就是 ClusterFuzz 发现的心脏滴血漏洞了。扩展性从官方文档上看,上面的例子只是用到了引导式 fuzz ,ClusterFuzz 还支持可任意扩展的黑盒 fuzz ,可支持使用 Python 编写 fuzz 生成器。此次由于时间关系未能尝试,有兴趣的同学可以尝试一下。同时官方的 local 文件夹中有看到 docker 运行相关的脚本,相信未来会支持通过 docker 运行,降低环境配置成本。局限性从官方文档中可以看到,被测试的软件需要在编译时插入一些桩用于检测异常,而这个方案目前仅支持 C/C++ ,且主要用于内存地址检测。而对于我们平时接触到的 Java/python/go 应用,没有提供对应的方案,需要另行扩展。小结及展望ClusterFuzz 正如其名,一个集群运行的 Fuzz 工具。它提供了执行机器人管理以及一个非常简便的管理界面,也做到了和研发流程无缝的接入,甚至更进一步地做到了 bug 自动创建及修复检测。从小的地方看,它让模糊测试通过集群获得了更高的执行效率和问题发现效率。从大的地方看,它提供的整体流程,包含了自动报 bug 和检测 bug 修复情况,让大家只在需要的时候感知到它的存在,正是目前大部分 CI 实践中欠缺的最后一公里路,缺陷的自动上报与修复检测,值得我们思考补全我们的 CI 流程。虽然目前并未提供除 C/C++ 之外的完整解决方案,但相信按照其扩展性,扩展到支持更多的语言并不是难题。期望未来有更多的同学参与扩展这个工具,形成开箱即用的解决方案。(end)谷歌 ClusterFuzz 尝鲜意犹未尽?想掌握更多测试前沿技术神兵利器?推荐关注 MTSC2019 互联网测试开发大会聚焦测试前沿技术趋势与创新发展分享质量管理案例与最佳实战经验MTSC 中国移动互联网测试开发大会(Mobile Testing Summit China)是由 TesterHome 社区主办的软件测试行业年度技术盛会。自 2015 年创办以来,MTSC 已成功举办 4 届。MTSC2019 第五届大会将于 2019 年 6 月 28~29 日在北京国际会议中心举行。大会官网:https://testerhome.com/mtsc/2019推荐演讲嘉宾特别福利目前,MTSC2019 面向软件测试行业全球公开征集议题,期待各位资深测试技术专家和质量管理经理贡献 Topic 或者推荐演讲嘉宾。推荐成功者,免费赠送一张 MTSC2019 大会门票。:)议题信息可发送邮件至: topic@testerhome.com往届 MTSC 大会风采 ...

February 28, 2019 · 3 min · jiezi

常用数据结构

字典:即map,映射,通过key=>value的方式直接查找与之对应的值,实现一般是hash表或二叉树跳跃表:本质是链表,只不过将数据进行提取分层,将总数据置为底层,提取2、4、的倍数为第一二层,查找时从高层进行二分查找。查找树:父结点大于左子结点,小于右子结点的树平衡树:将有单一子结点的父结点进行旋转折叠,成为一个查找树B树:B树是一颗有任意(一般固定小于某个值)子结点数的平衡树,B树的插入删除可能会导致结点的分裂和合并;B+树:B+树是一棵最底层子结点包含所有元素,父结点会有重复的B树,B+树遍历方便,可在子结点之间旋转, innodb使用它可以进行范围选择;红黑树:是一种每个节点都带有颜色属性的二叉树,它的根和叶子都是黑色,每个红色节点必须有两个黑色的子节点,从任一节点到其每个叶子的所有简单路径都包含相同数目的黑色节点。AVL树是左右两棵子树层级差不超过 1的二叉树,如果插入的数据使得子数层级相差大于1,则需要进行一次或多次树旋转来重新达到平衡。Trie树是一种前缀树,如四叉树。

February 27, 2019 · 1 min · jiezi

Hash 的应用

Hash 的应用@(算法)注:本文所讨论的Hash只讲诉其在机试试题解答中的应用。题目描述:给你n个整数,请按从大到小的顺序输出其中前m大的数输入:每组测试数据有两行,第一行有两个数n,m(0<n,m<1000000),第二行包含n个各不相同,且都处于区间[-500000,500000]的整数输出:对每组测试数据按从大到小的顺序输出前m大的数我们很容易会想到,用sort()函数将其降序排序,然后输出前m大的数。多么简单的一道题啊,不禁让我感到疑惑,是否忽略了什么?在本题中,如果使用快排来解决该题,由于待排数字的数量十分庞大(1000000),根据快排的时间复杂度O(nlogn) ,其时间复杂度将会达到千万级。所以,我们并不能使用快速排序来解决本题。代码块#define OFFSET 500000int hash[1000000];int main() { int n, m; while (scanf("%d%d",&n,&m)!=EOF &&n!=0) { for (int i = -500000; i <= 500000; i++) { hash[i + OFFSET] = 0; } for (int i = 0; i < n; i++) { int x; scanf("%d", &x); hash[x + OFFSET]=1; } for (int i = 1000000; i >= m+OFFSET; i–) { if (hash[i]==1) { printf("%d “, i - OFFSET); } } } return 0;}代码解读#define OFFSET 500000 由于输入数据出现负数,于是我们不能直接把输入数据当做数组下标来访问数组元素,而是将每一个输入的数组都加上一个固定的偏移量,使输入数据的[-500000,500000]区间被映射到数组下标的[0,1000000]区间。无疑,这是一种在时间上更加高效的排序方法。参考资料:计算机考研——机试指南[电子工业出版社] ...

February 26, 2019 · 1 min · jiezi

算法复习

排序堆栈、队列、链表递归波兰式和逆波兰式

February 26, 2019 · 1 min · jiezi

Redis 的 string

Redis 的 stringRedis 的字符串就是 SET 和 GET 操作对应的类型,算是 Redis 里最常用的类型了。0x00 动态字符串 sdsRedis 内部的字符串表示,没有直接使用 C 语言字符串,而是对其进行了一定的改造,改造后的字符串在内存管理和长度计算方面的性能都有所提升。举个例子,假设要存储的是字符串”redis“。+——–+——–+————-+| len | alloc | |r|e|d|i|s| |+——–+——–++———–++ | | v v flag ‘\0’这个图就是 sds 的内存结构。sdshdr 分四个部分,从左往右一次是字符串长度、开辟的内存空间、sdshdr 的类型以及字符串本身。在字符串初始化好之后,会返回一个指针,指向字符串本身的首地址,也就是 r 的内存地址。这样,既能方便地享受 C 语言字符串带来的兼容性,又可以对内存管理了如指掌。0x01 redisObjtypedef struct redisObject { unsigned type:4; unsigned encoding:4; unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or * LFU data (least significant 8 bits frequency * and most significant 16 bits access time). */ int refcount; void *ptr;} robj;这是 Redis 的对象的结构,每一个 key 或者 value 都会存储成这样一个结构体。可以看一下第二个成员变量 encoding。这是 Redis 对这个对象做的编码操作。,Redis string 在存储的时候,会努力进行三种方式的编码,分别是 OBJ_ENCODING_RAW、OBJ_ENCODING_INT 和 OBJ_ENCODING_EMBSTR,针对这三种编码,尽可能地进行去优化内存空间。最后一个成员变量则是指向具体的内容,如字符串类型的对象就直接指向对应的 sds 字符串的地址。0x02 OBJ_ENCODING_INTRedis 在拿到了用户 SET 命令的字符串后,如果用户 SET 了一个纯数字字符串,就会做这种优化。这种优化有一个好处,可以看到 redisObject 中的 ptr 变量,这是一个指针,也就意味着,这个变量占据 8 个字节的内存空间。8 个字节的内存空间,刚好可以存储一个 64 位的整数。Redis 会判断字符串的长度是不是小于 20,如果小于 20 则会尝试将字符串整数化。这里的 20 是因为 64 位整型可以表示的范围是 [-9223372036854775808,9223372036854775807]。这样做了之后,就不需要额外的空间为这个“字符串”生成一个动态字符串,直接存在 ptr 就可以了。0x03 OBJ_ENCODING_EMBSTRRedis 在转换成整型失败后,就会尝试这种编码。这种编码其实是把 robj 和 sds 字符串放在了同一块内存空间中。这种编码对内存空间优化不大,但是它们的空间是连续的。这样做,我认为有几个好处。开辟和回收空间的时候,只需要进行一次操作就可以了,这样做减少了 malloc 和 free 的次数。连续的空间,对系统缓存更加友好。目前,是对长度小于 44 的小字符串进行这种编码。0x04 OBJ_ENCODING_RAW在前面的几种尝试都失败之后,就只能存储成最原始的动态字符串了。但是 Redis 在这里依然还是会做一点事情,就是会把动态字符串的多余的空间给释放掉,目前(5.0)是会释放掉 10% 长度的冗余空间,如果不足 10% 就不会释放。0x05 后记Redis 对内存的优化真是做到了极致,这里还只是冰山一角,我了解到的还只是冰山一角。本文为作者自己读书总结的文章,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。 ...

February 26, 2019 · 1 min · jiezi

日期类问题

日期类问题@(算法)日期类问题中最基本的问题——求两个日期间的天数差。解决这类区间问题有一个统一的思想——把原区间问题统一到起点确定的区间问题上去。日期类问题有一个特别需要注意的要点——闰年闰年的判断规则:当年数不能被100整除时若能被4整除,或者其能被400整除时也是闰年。用逻辑表达式为: Year%100!=0 && Year%4==0 || Year%400==0例题题目描述We now use the Gregorian style of dating in Russia. The leap years are years with number divisible by 4 but not divisible by 100, or divisible by 400.For example, years 2004, 2180 and 2400 are leap. Years 2004, 2181 and 2300 are not leap.Your task is to write a program which will compute the day of week corresponding to a given date in the nearest past or in the future using today’s agreement about dating.输入There is one single line contains the day number d, month name M and year number y(1000≤y≤3000). The month name is the corresponding English name starting from the capital letter.输出Output a single line with the English name of the day of week corresponding to the date, starting from the capital letter. All other letters must be in lower case.样例输入9 October 2001 14 October 2001样例输入TuesdaySunday代码块// task.cpp : 定义控制台应用程序的入口点。//#include “stdafx.h”#include <iostream>#include <cstdio>#include <cstdlib>#define ISYEAR(x) x%100!=0 && x%4==0 || x%400==0?1:0//判断是否是闰年using namespace std;//每月的天数int dayOfMonth[13][2] = { 0,0, 31,31, 28,29, 31,31, 30,30, 31,31, 30,30, 31,31, 31,31, 30,30, 31,31, 30,30, 31,31};struct Date { int Year; int Month; int Day; void nextDay() { //计算下一天的日期 Day++; if (Day > dayOfMonth[Month][ISYEAR(Year)]) { Day = 1; Month++; if (Month > 12) { Year++; Month = 1; } } }};//每个月的名称char monthName[13][20] = { “”, “January”, “February”, “March”, “April”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December”};//每天的名称char weekName[7][20] = { “Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”};int buf[3001][13][32];int main(){ Date date; int t = 0; date.Year = 0; date.Month = 1; date.Day = 1; while (date.Year != 3001) { buf[date.Year][date.Month][date.Day] = t; date.nextDay(); t++; } int d, m, y; char s[20]; while (scanf("%d%s%d", &d, s, &y) != EOF) { for (m = 1; m <= 12; m++) { if (strcmp(s, monthName[m])==0) {//将输入字符串与月名比较得出月数 break; } } int days = buf[y][m][d] - buf[2019][2][26]; days += 2; //今天是2019.02.26,星期二,故 days+2 puts(weekName[(days % 7 + 7) % 7]); //用7对其取模,并且保证其为非负数 } return 0;}总结1.善用结构体2.判断闰年:#define ISYEAR(x) x%100!=0 && x%4==0 || x%400==0?1:03.循环nextDay(),将日期转换成int整型,把原区间问题统一到起点确定的区间问题上去。 int days = buf[y][m][d] - buf[2019][2][26]; days += 2; //今天是2019.02.26,星期二,故 days+2 puts(weekName[(days % 7 + 7) % 7]); //用7对其取模,并且保证其为非负数关键代码! int days = buf[y][m][d] - buf[2019][2][26]可能为负数;days % 7 + 7) % 7用7对其取模,保证其为非负数。参考资料:计算机考研——机试指南[电子工业出版社] ...

February 26, 2019 · 2 min · jiezi

【NEO NEXT社区】高校区块链技术分享会——上海交通大学

寒假假期转眼即逝浪完之后该学习了….. 这不,学习的好机会到啦 ……注意,文末有超大福利高校区块链技术分享会NEO-NEXT社区高校公开课——高校区块链技术分享会,是NEO-NEXT社区为推进区块链行业专业人才的发掘及培养,为给《NEO DAPP全球区块链应用开发挑战赛》注入新鲜血液举办的高校系列活动。本次分享会我们邀请了高校老师、行业媒体、区块链项目方核心技术人员、项目发起人从基础认知、技术解析、就业前景、未来发展等各个方面与高校学生来一次面对面的探讨交流。简单深入地向同学们普及区块链知识,讲解区块链技术上的很多有趣的地方。我们很希望此次分享会可以为潜在的高校区块链人才提供一个了解区块链、认识区块链的机会。活动详情png](/img/bVboRhX)分享会时间:3月6日(周三)15:00—17:00分享会地点:上海交通大学闵行校区(具体地点后续通知)到场嘉宾活动现场亮点 价值百元的区块链系列课程、项目周边大礼包将在活动现场抽奖环节送出此次分享会除了技术分享外 我们还将招募优秀的技术人才、UI、产品 所以同学们来现场时可带上自己的简历 …..活动流程14:50:—15:00:签到入场(递交简历)15:00—15:05:主持人开场15:05—15:20:NEXT社区发起人王岳峰介绍社区概况15:20—15:40:高校老师主题分享15:40—15:50:抽奖环节15:50—16:10:阿基米德CTO齐峰主题分享16:10—16:30:历链发起人艾迪生《如何设计低用户门槛的dApp》16:30—16:50:神秘嘉宾主题分享16:50—17:00:答疑环节(偷偷告诉你提问有奖品哦)— 扫我报名 — — 为什么举办 — — 高校区块链技术分享会 — 年轻、富有激情的思维和创造力是很多经验和阅历无法弥补的,因此高校群体是我们NEXT社区极为看重的一个社区拓展方向之一。我们希望通过此次高校区块链技术分享会,可以为NEO DAPP全球区块链应用开发挑战赛注入新鲜血液。为对区块链感兴趣、潜心研究区块链技术的大学生提供一个与专业区块链从业人员思想碰撞的平台。同时希望通过此次大赛为行业储备区块链技术人才。另外,我们会从每个高校评选出1-2名大赛决赛陪审团成员,参与决赛评选,共同见证最终冠军的诞生。大家可在报名表中选择是否加入,如果对陪审团有疑问的同学,可在分享会当天提问,或者添加客服微信(微信号:neo_next1)。

February 25, 2019 · 1 min · jiezi

ubuntu下Nginx详解及点播直播服务器搭建

1 Nginx简介 Nginx(engine x)是一个高性能的HTTP服务器,也是一款轻量级的Web服务器,反向代理服务器及电子邮件IMAP/POP3/SMTP代理服务器。Nginx是由伊戈尔·赛索耶夫为站点Rambler.ru开发的。第一个公开版本发布于2004年10月4日。其将源代码以类BSD许可证的形式发布,因它的稳定性、丰富的功能集、示列配置文件和低系统资源的消耗而闻名。其特点是占用内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现比较好,中国大陆使用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。2 Nginx的特点2.1 动静分离 Nginx是一种轻量级,高性能,多进程的Web服务器,非常适合作为静态资源服务器使用,而动态的访问操作可以是用稳定的Apache,Tomcat及IIS等来实现。这里就以Nginx作为代理服务器的同时,也使用其作为静态资源服务器。静态资源服务器通过绝对路径去访问,放在nginx服务器当中。动态资源通过url拼接字符串的方式去访问例如tomcat服务器2.2 均衡负载2.2.1 Nginx的upstream目前支持以下几种方式的分配 1).轮询(默认) 每个请求按实际顺序逐一分配到不同的后端服务器,如果后端服务器dump掉,能自动剔除。 2) weight(权重) 为后台服务器指定轮询几率,weight和访问量成正比,让性能高的服务器承担更多的访问量。 3).ip_hash 每个请求按访问ip的hash结构分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 4).fair(第三方) 按后端服务器的响应时间来分配请求,响应时间快短的优先分配。 5).url_hash(第三方) 按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。2.2.2 Session问题 当我们确定一系列负载的服务器后,如果你先访问到A服务器,下一次请求突然转到B服务器。这时候与A服务器建立的Session,传到B服务器肯定是无法响应的。下面我们来看一下解决方案: 1).Session或凭据缓存到独立的服务器上 将seesion保存到独立的服务器,缓存效率会比较高,但如果同时访问的服务器过多的话,可能导致session服务器无法负荷而宕机。 2).Session或凭据保存数据库中 保存到数据中,除了要控制Session的有效期,同时也加重了数据库的负担,所以最终转变为SQL Server负载均衡,涉及读,写,过期同步,处理起来会很复杂。 3).nginx ip_hash保持同一IP的请求都是指定到固定的一台服务器 通过nginx ip_hash负载保持对同一服务器的会话,这种方式最方便,最轻量。2.2.3 文件的上传下载 如果实现了均衡负载,除了Session问题,我们还会碰到文件的上传下载问题。文件不可能上传不同的服务器上,这样会导致下载不到对应文件的问题。下面来看一下解决方案: 1).独立文件服务器 2).文件压缩数据库 两种方案都是常用的,我们来说一下文件压缩数据库,以前的方式都是将文件二进制压缩至关系型数据库,而现在NOSQL的流行,加上MongoDB处理文件又比较方便,所以文件压库又多了一种选择。毕竟文件服务器的效率和管理以及安全都不及数据库。2.3 反向代理 反向代理(Reverse Proxy)方式是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。 反向代理服务器: 通常的代理服务器,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。当一个代理服务器能够代理外部网络上的主机,访问内部网络时,这种代理服务的方式称为反向代理服务器。2.4 单点故障 某台节点服务器挂了,但是Nginx仍然会可能选中这个出故障的机器,然后就一直连接着是因为超时时间很长,具体多长不清楚,所以为了避免一直连接着,我们需要设置超时时间。 用Keepalived搭建双Nginx server集群,防止单点故障2.5.优化2.5.1 高层的配置(nginx.conf)worker_processes auto; worker_rlimit_nofile 100000;worker_cpu_affinity 0001 0010 0100 1000 0001 0010 0100 1000;worker_processes: 定义了nginx对外提供web服务时的worker进程数。最优值取决于许多因素,包括(但不限于)CPU核的数量、存储数据的硬盘数量及负载模式。不能确定的时候,将其设置为可用的CPU内核数将是一个好的开始(设置为“auto”将尝试自动检测它)。worker_rlimit_nofile: 更改worker进程的最大打开文件数限制。如果没设置的话,这个值为操作系统的限制。设置后你的操作系统和Nginx可以处理比“ulimit -a”更多的文件,所以把这个值设高,这样nginx就不会有“too many open files”问题了。worker_cpu_affinity: CPU亲和力配置,让不同的进程绑定不同的CPU,可以减少由于线程切换时CPU切换带来的缓存拷贝导致降低效率。2.5.2 Events模块 events模块中包含Nginx中所有处理连接的设置。events {worker_connections 2048; multi_accept on; use epoll; }worker_connections: 设置可由一个worker进程同时打开的最大连接数。如果设置了上面提到的worker_rlimit_nofile,我们可以将这个值设得很高。但是不能超过系统的可用socket连接数限制(~ 64K)。multi_accept: 告诉nginx收到一个新连接通知后接受尽可能多的连接。use 设置用于复用客户端线程的轮询方法。如果你使用Linux 2.6+,你应该使用epoll。如果你使用FreeBSD,你应该使用kqueue。(值得注意的是如果你不知道Nginx该使用哪种轮询方法的话,它会选择一个最适合你操作系统的)2.5.3 HTTP 模块 HTTP模块控制着Nginx http处理的所有核心特性。http { server_tokens off; sendfile on; tcp_nopush on; tcp_nodelay on; … } server_tokens: 并不会让nginx执行的速度更快,但它可以关闭在错误页面中的nginx版本数字,这样对于安全性是有好处的。sendfile: 可以让sendfile()发挥作用。sendfile()可以在磁盘和TCP socket之间互相拷贝数据(或任意两个文件描述符)。Pre-sendfile是传送数据之前在用户空间申请数据缓冲区。之后用read()将数据从文件拷贝到这个缓冲区,write()将缓冲区数据写入网络。sendfile()是立即将数据从磁盘读到OS缓存。因为这种拷贝是在内核完成的,sendfile()要比组合read()和write()以及打开关闭丢弃缓冲更加有效(更多有关于sendfile)。tcp_nopush: 告诉nginx在一个数据包里发送所有头文件,而不一个接一个的发送。tcp_nodelay: 告诉nginx不要缓存数据,而是一段一段的发送–当需要及时发送数据时,就应该给应用设置这个属性,这样发送一小块数据信息时就不能立即得到返回值。access_log off; error_log /var/log/nginx/error.log crit; access_log: 设置nginx是否将存储访问日志。关闭这个选项可以让读取磁盘IO操作更快(aka,YOLO)error_log: 告诉nginx只能记录严重的错误:3 Nginx编译 nginx依赖于pcre, openssl, zlib, nginx-rtmp-module。故在编译nginx之前必须下载先编译这些库。 pcre是一个Perl库,包括perl兼容的正则表达式库。 zlib提供数据压缩用的函式库。 nginx-rtmp-module是nginx的rtmp流媒体服务拓展库,实现了rtmp,hls,dash的流媒体服务功能。下面是本人编写的nginx一键下载编译脚本,运行该脚步即可下载编译nginx:#!/bin/bash#indicate that this script execute by /bin/bash# 本脚本用于自动下载nginx已经依赖库pcre, openssl, zlib, nginx-rtmp-module,并编译安装,安装时需要输入管理员账号权限#zipexts=(".tar.gz" “.tar.bz2” “.tar.Z” “.tgz” “.tar” “.gz” “.bz2” “.Z” “.rar” “.zip”)#zipexts=(".tar.gz" “.tar.bz2” “.tar.Z” “.tgz” “.tar” “.gz” “.bz2” “.Z” “.rar” “.zip”)# 由于shell脚本的函数不能返回字符串,故设置全局变量以便记录dezip产生的返回值以及下downloazanddezip函数下载解压后的文件夹名称extname=““outputname=””# 解压压缩包,并返回解压后的文件夹或文件名称# param1:压缩包名称,必填项# param2:解压到指定文件夹,可为空# 返回值:无, 注,shell返回值只能为整形,不能为字符串dezip(){ outputname=$2 extname="" exename=“tar” unzipflag="" echo “param1:"$1 # 检测压缩包类型,并使用对应的解压方式 if expr match $1 “..tar.gz” != 0; then extname=".tar.gz” exename=“tar” unzipflag="-zxvf" #echo $extname elif expr match $1 “..tar.bz2” != 0; then extname=".tar.bz2" exename=“tar” unzipflag="-xjf" #echo $extname elif expr match $1 “..tar” != 0; then extname=".tar" exename=“tar” unzipflag="-xvf" elif expr match $1 “..tgz” != 0; then extname=".tgz" exename=“tar” unzipflag="-xvf" elif expr match $1 “..gz” != 0; then extname=".gz" exename=“gzip” # 或gunzip unzipflag="-d" elif expr match $1 “..bz2” != 0; then extname=".bz2" exename=“bzip2” # 或bunzip2 unzipflag="-d" elif expr match $1 “..tar.Z” != 0; then extname=".tar.Z" exename=“tar” unzipflag="-xZf" elif expr match $1 “.Z” != 0; then extname=".Z" exename=“uncompress” unzipflag="" elif expr match $1 “..rar” != 0; then extname=".rar" exename=“unrar” unzipflag=“e” elif expr match $1 “..zip” != 0; then extname=".zip" exename=“unzip” unzipflag="-o" fi if [ ! -d $outputname ]; then outputname=${1%$extname} echo “outputname” $outputname fi #if [ ! -d $outputname ]; then #mkdir $outputname #echo $outputname #fi echo “$exename $unzipflag $1” # 解压文件 $exename $unzipflag $1 #return $extname}# 下载压缩包并解压# param1:url# param2:zip# return: dezip filenamedownloazanddezip(){ if [ -d $1 ]; then echo “Invalid url, please input url while you run downloazanddezip” fi downloadurl=$1 # 截取?前面的字符串 downloadurl=${downloadurl%?} echo $downloadurl # 截取最后一个/后面的字符串,即压缩包名称 zipname=${downloadurl##/} echo $zipname if [ ! -f $zipname ]; then curl -o $zipname $1 echo ‘curl -o $zipname $1’ fi echo “dezip:"$zipname dezip $zipname #extname=$? echo $extname’=dezip’ $zipname if expr match $zipname $extname == 0; then zipname=${zipname%$extname} echo “zipname:"$zipname fi outputname=$zipname}# 下载nginx源码,如果链接有误,直接替换链接即可curdir=$PWDdownloazanddezip http://117.128.6.28/cache/nginx.org/download/nginx-1.14.2.tar.gz?ich_args2=468-23103716018527_5b6f2632bf91b3cdae35405cfb43338b_10001002_9c89612cdfcbf8d59538518939a83798_ff478ef610d500dd7f20fe13dd251d7b# 记录nginx库源码文件夹目录名称nginxname=$outputnamecd $outputnameif [ ! -d “./thirdpart/” ];then mkdir thirdpartficd ./thirdpart# 下载pcre库downloazanddezip https://ftp.pcre.org/pub/pcre/pcre-8.42.zip# 记录pcre库源码文件夹目录名称pcrename=$outputname# 下载openssldownloazanddezip https://www.openssl.org/source/openssl-1.0.2q.tar.gz# 记录openssl库源码文件夹目录名称opensslname=$outputname# 下载zlib库downloazanddezip http://117.128.6.17/cache/www.zlib.net/zlib-1.2.11.tar.gz?ich_args2=531-22232503018317_67f53fed3443a8995b0d31f74b74bce1_10001002_9c89612cdfc7f8d09639518939a83798_9b3b5eb8fa2f98e0b42e37f3838be97f# 记录zlib库源码文件夹目录名称zlibname=$outputname# 从github下载nginx-rtmp-module源码if [ ! -d “./nginx-rtmp-module/” ];then git clone https://github.com/arut/nginx-rtmp-module.gitficd ../make clean./configure –prefix=$curdir/$nginxname/build –with-openssl=$curdir/$nginxname/thirdpart/$opensslname –with-pcre=$curdir/$nginxname/thirdpart/$pcrename –with-zlib=$curdir/$nginxname/thirdpart/$zlibname –add-module=$curdir/$nginxname/thirdpart/nginx-rtmp-module –with-http_ssl_modulemakemake install4 配置点播服务4.1 修改nginx.conf 打开build目录下conf文件夹下的nginx.conf文件:http { include mime.types; default_type application/octet-stream; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; # add stat page location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root $nginxpath/thirdpart/nginx-rtmp-module; } # add stat page end location / { root html; index index.html index.htm; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # 支持flv、mp4文件点播 location ~* .flv$ {#flv 支持 root $flvpath;#flv点播文件目录 } location ~* .mp4$ {#MP4 支持 root $mp4path;#mp4点播文件目录 } }4.2 测试点播服务 运行nginx:$sudo ./nginx -c $buildpath/conf/nginx.conf 然后打开浏览器访问:http://127.0.0.1/test.mp4如果能够正常播放,说明点播服务配置成功。5 配置直播服务5.1 修改nginx.conf配置:http { include mime.types; default_type application/octet-stream; #log_format main ‘$remote_addr - $remote_user [$time_local] “$request” ’ # ‘$status $body_bytes_sent “$http_referer” ’ # ‘"$http_user_agent” “$http_x_forwarded_for”’; #access_log logs/access.log main; sendfile on; #tcp_nopush on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; server { listen 80; server_name localhost; location /stat { rtmp_stat all; rtmp_stat_stylesheet stat.xsl; } location /stat.xsl { root $nginxpath/third/nginx-rtmp-module; } location / { root html; index index.html index.htm; } location /live { #这里也是需要添加的字段。 types { application/vnd.apple.mpegurl m3u8; video/mp2t ts; } alias /opt/video/hls; expires -1; add_header Cache-Control no-cache; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } # 支持flv、mp4文件点播 location ~* .flv$ {#flv 支持 root $flvpath;#flv点播文件目录 } location ~* .mp4$ {#MP4 支持 root $mp4path;#mp4点播文件目录 } }}# 增加rtmp服务rtmp { server { listen 1935; chunk_size 4096; # rtmp点播 application vod { play $rtmpvodpath;#rtmp点播文件存放路径 } # rtmp直播 application live { live on; hls on; #这个参数把直播服务器改造成实时回放服务器。 wait_key on; #对视频切片进行保护,这样就不会产生马赛克了。 hls_path $hls_save_path; #切片视频文件存放位置。 hls_fragment 10s; #每个视频切片的时长。 hls_playlist_length 60s; #总共可以回看的事件,这里设置的是1分钟。 hls_continuous on; #连续模式。 hls_cleanup on; #对多余的切片进行删除。 hls_nested on; #嵌套模式。 } }}5.2 测试rtmp直播服务 安装ffmpeg,并使用ffmpeg进行rtmp推流$ sudo apt-get install ffmpeg$ ffmpeg -re -I $mp4path/test.mp4 -vcodec libx264 -acodec aac -strict -2 -f flv rtmp://localhost:1935/live$ ffplay -I rtmp://localhost:1935/live 若能正常播放rtmp流,则rtmp直播服务发布成功。 ...

February 25, 2019 · 4 min · jiezi

素数

素数@(算法)素数简介质数(prime number)又称素数。质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。还能被其他数(0除外)整除的数为合数。判断一个数是否是素数根据定义,除了1和本身之外没有其他约束,所以判断是否为质数,根据定义直接判断从2到n-1是否存在n的约数。方法一:暴力破解!bool isPrime(int num){ for(int i=2;i<num;i++){ if(num%i ==0){ return 0; } } return 1;}上述方法,明显存在效率极低的问题。一个数若可以进行因式分解,那么分解时得到的两个数,一定是一个小于等于sqrt(n),一个大于等于sqrt(n)改进:bool isPrime(int num){ int t=sqrt(num); for(int i=2;i<num;i++){ if(num%i ==0){ return 0; } } return 1;}方法二:素数筛法可以提前处理出来1n 的全体素数1.把1n列出来2.去掉不是特殊的13.从小到大,枚举每一个没有删掉的数字i把 i 的2倍,3倍,4倍,…,删掉剩下的没被删掉的都是素数const int N=100;int notprime[N];int main(){ notprime[1]=1; for(int i=2;i<N;i++){ if(notprime[i]==0){ for(int j=i+i;j<N;j=j+i){ ////删掉2i,3i,4*i…… notprime[j]=1; } } } for(int i=1;i<N;i++){ if(notprime[i]!=1){ printf("%d\t",i); } } return 0;}方法三:欧拉筛法在素数筛法中,有很多合数被删除多次。而欧拉筛法提供两个数组,一个是素数表,另一个是删除合数表(值为1表示表示不是素数)。const int N=100;int notprime[N]; //删除标记,值为1表示表示不是素数int prime[N]; //素数表int main(){ int t=0; //初始化素数表为空 notprime[1]=1; for(int i=2;i<N;i++){ if(notprime[i]==0){ //找到一个没有被删除的数 prime[t++]=i; //加入素数表 } for(int j=0;j<t&&prime[j]*i<N;j++){ //枚举素数表 notprime[prime[j]*i]=1; if(i%prime[j]==0){ break; //保证了每个合数只会被它的最小素因子筛掉 } } } for(int i=0;i<N;i++){ if(prime[i]!=0){ printf("%d\t",prime[i]); } }}prime[]数组中的素数是递增的,当i能整除prime[j],那么 prime[j]*i 这个合数肯定被prime[j]乘以某个数筛掉。i%prime[j]==0保证了每个合数只会被它的最小素因子筛掉。参考[1]http://www.cnblogs.com/zhuoha…[2]http://www.cnblogs.com/zhuoha…[3]https://baike.baidu.com/item/… ...

February 24, 2019 · 1 min · jiezi

C语言笔记(第一章:C语言编程)

第一章:C语言编程标签(空格分隔): C语言学习本章主要内容C语言标准标准库的概念如何创建C程序如何组织C程序如何编写在屏幕上显示文字的程序1.C语言标准任何物品事件的使用都需要一个大家都认同的使用规则,如同游戏一样,大家需要遵守同一个游戏规则,才能更好的使用。1989 年国际标准组织 ISO 采纳了美国国家标准协会(ANSI)对于C语言的标准化,此时C语言又被称为 ANSI C。正式发布后官方名称——ISO/IEC 9899: 1990,简称:C89/90 标准1999 年C语言标准委员会对C语言进行了改进,正式发布了 ISO/IEC 9899: 1999,简称:C99 标准2007 年,C语言标准委员会又重新开始修订C语言,到了 2011 年正式发布了 ISO/IEC 9899 : 2011,简称:C11 标准。C标准的详细介绍——维基百科2.标准库的概念概念: 标准库定义了编写C程序时常常需要的常量、符号和函数。同时提供了基本C语言的一些可选扩展。位置:标准库在一系列标准文件——头文件中指定,头文件的扩展名总是.h。例如: < assert.h >—定义awwert和static_asssert宏C标准库也称为ISO C库,是用于完成诸如输入/输出处理、字符串处理、内存管理、数学计算和许多其他操作系统服务等任务的宏、类型和函数的集合。它是在C标准中(例如C11标准)中定义的。3.创建C程序创建C程序有四个基本的过程编辑编译链接执行1.编辑编辑的过程就是创建和修改C程序的源代码——我们编写的程序指令称为源代码。编译器:是提供了编写,管理,开发与测试的环境也称为集成开发环境(Integrade Developmen Environment,IDE)2.编译编译器将源代码转换成为机器语言,并且在编译的过程中,找出并报告错误。编译器能找出程序中很多的无效或无法识别的错误,以及结构错误。源文件就是用汇编语言或高级语言写出来的代码保存为文件后的结果。扩展名为:.C编译器的输出结果称为——对象代码(object code),存放对象代码的文件称为对象文件(object file)如果程序有错误则阻止对象程序和文件的创建如果程序没有错误则编译成功,会生成一个与源文件同名的文件扩展名为.obj(Windows环境)/.o(Linx/UNIX环境)3.链接通过链接器把源文件和对象文件以及必须的代码模块组合成一个新的文件。链接器:将源代码文件中由编译器产生的各种对象模块组合起来,再从C语言提供的程序库中添加必要的代码模块,将他们组合成一个可执行的文件。扩展名为:.exe连接器可以检测和报告错误。4.执行当成功完成了上述三个阶段后,运行程序。程序运行的流程图如下:st=>start: 开始e=>end: 成功op1=>operation: <编辑>创建/修改程序源代码op2=>operation: 源文件(.C)op3=>operation: <编译>生成机器指令cond1=>condition: 成功?op4=>operation: 对象文件(.obj)op5=>operation: <链接>链接源代码文件库等cond2=>condition: 成功?op6=>operation: 可执行文件(.exe)op7=>operation: <执行>运行程序cond3=>condition: 成功?st->op1->op2->op3->cond1cond1(no)->op1->op2->op3->cond1cond1(yes)->op4->op5->cond2cond2(no)->op1->op2->op3->cond1cond2(yes)->op6->op7->cond3cond3(no)->op1->op2->op3->cond1cond3(yes)->e

February 21, 2019 · 1 min · jiezi

Mac/Linux 安装中文版 man 帮助命令

一份攻城狮笔记有哪些鲜为人知,但是很有意思的网站?每天搜集 Github 上优秀的项目一些有趣的民间故事超好用的谷歌浏览器、Sublime Text、Phpstorm、油猴插件合集linux 环境安装macOS 环境安装编译工具安装因为需要编译安装,所以你电脑上需要有编译工具,运行下面两个命令安装$ brew install automake$ brew install opencc安装# 进入下载目录$ cd ~/Downloads/# 下载最新版本的源码包$ wget https://github.com/man-pages-zh/manpages-zh/archive/v1.6.3.3.tar.gz# 解压源码包(atool命令,推荐安装这个工具,统一所有压缩文档的命令)$ atool -x v1.6.3.3.tar.gz# 或者使用这个命令解压$ tar zxvf v1.6.3.3.tar.gz# 进入源码包文件夹$ cd manpages-zh-1.6.3.2/# 编译安装 1$ autoreconf –install –force# 编译安装 2$ ./configure# 编译安装 3$ sudo make# 编译安装 4$ sudo make install# 配置别名(如果是 zsh 对应处理即可)$ echo “alias cman=‘man -M /usr/local/share/man/zh_CN’” >> ~/.bash_profile# 使别名生效$ source ~/.bash_profile# 我们就安装上了中文版本的 man 工具了,但是运行命令会发现乱码。cman ls安装 groff 新版本解决中文乱码的问题# 进入下载目录$ cd ~/Downloads/# 下载1.22版本的源码包$ wget http://git.savannah.gnu.org/cgit/groff.git/snapshot/groff-1.22.tar.gz# 解压$ atool -x groff-1.22.tar.gz# 进入目录$ cd groff-1.22# 编译安装$ ./configure$ sudo make$ sudo make install# 打开配置文件$ sudo vim /etc/man.conf# 进入编辑器之后,在文件末尾添加NROFF preconv -e UTF8 | /usr/local/bin/nroff -Tutf8 -mandoc -c# 保存退出# 运行命令,完美解决乱码问题cman ls ...

February 21, 2019 · 1 min · jiezi

《深入理解计算机系统》读书笔记:5.5 vs 5.6

0x00 前言没有看过或者没有看到这里的小伙伴们,看到这个标题一定觉得摸不着头脑。那这里就先来解释一下背景。double poly(double a[], double x, long degree){ long i; double result = a[0]; double xpwr = x; for (i = 1; i <= degree; i++) { result += a[i] * xpwr; xpwr = x * xpwr; } return result;}double polyh(double a[], double x, long degree){ long i; double result = a[degree]; for (i = degree; i >= 0; i–) { result = a[i] + x * result; } return result;}这是 CSAPP 的两道题,每一题是一段代码,这两段代码实现了同一个功能。这两道题有一个共同的问题,比较这两段代码的性能。0x01 答案这里的答案是,poly 的性能比 polyh 的性能要高。poly 的 CPE 是 5,而 polyh 的 CPE 是 8。这就显得很尴尬了,我原以为两个函数的 CPE 都是 8。0x02 我的猜想polyh 的 CPE 是 8 我没有疑问,因为这个循环里的操作是无法并行的,也就是下一次迭代会依赖上一次迭代产生的结果。所以,CPE = 5 + 3,5 是浮点数乘法的延迟下届,3 是浮点数加法的延迟下界。poly 的 CPE 我原本认为也是 8,两个乘法是可以并行的,但是这个加法的是依赖于第一个乘法的值,无法并行,所以 CPE = 5 + 3 = 8。0x03 指令集并行和流水线上面的是我的猜想,所以我认为这里的答案是它们的 CPE 是相同的,性能也是相同的。但是如前面所写,答案并不是这样的。于是,我把之前看的东西都翻出来想了一下,真的不是这样的。现代 CPU 是有一个流水线的概念的。什么是流水线呢,想象一下汽车车间,我们造一辆汽车,是分成了很多道工序的,比如装配发动机、装车门、轮子等等。现代 CPU 也是类似的,我们看到的一条指令,在执行的时候,经历了一长串的流水线,导致了指令真正的执行顺序和我们看到的可能是不一样的,但是由于现代出来的这种机制,可以确保最后的结果是和我们看到的是一样的。0x04 解释poly 函数,在执行的时候,由于有两个浮点数乘法单元,所以 a[i] * xpwr 和 xpwr = x * xpwr 可以并行执行。而 a[i] * xpwr 可以通过流水线的数据转移,让这个加法 result + a[i] * xpwr 可以在下一次迭代的时候执行,因为每次迭代的时候,两个乘法都不会依赖 result 这个结果。这样,加法和乘法可以并行执行。浮点乘法的延迟下界是 5,浮点加法的延迟下界是 3,所以浮点乘法是关键路径,CPE 也自然就是 5 了。再来看看 polyh 函数。这个函数的循环里只有一个浮点乘法运算和一个浮点加法运算。先来看看浮点乘法运算,x * result,很显然,每一次乘法都需要依赖上一次迭代的结果,导致了加法无法和乘法并行执行。于是,CPE 就成了 5 + 3 = 8 了。0x05 最后这个例子,我觉得很有趣,因为它涉及到了一个流水线的细节。同时,也说明了,并不是操作少的代码,效率就高。本文为作者自己读书总结的文章,由于作者的水平限制,难免会有错误,欢迎大家指正,感激不尽。0x06 参考文献《深入理解计算机系统(第 3 版)》第 4、5 章 ...

February 19, 2019 · 1 min · jiezi

C 语言之柔性数组

一 历史在c99标准出来之前。如果要在某个结构体中使用字符串变时,为了使字符串变量存储地址能与结构体整体连在一起,需要这样实现#include <stdio.h>#include <malloc.h>#include <string.h>typedef struct pen{ int len; char data;//字符串变量}pen;int main(int argc, char argv){ char str[] = “this is a string”;//需要填入的字符串 / 动态申请一个pen类型结构体变量, 它的大小为,pen类型的本身长度, 再加上str(需要填入字符串的长度),再加1, / struct pen p = (struct pen)malloc(sizeof(pen) + strlen(str) + 1); p->data= NULL; //设置p的长度为目标字符串的长度 p->len = strlen(str); / 将目标字符串拷贝到结构体对应的位置 此处为什么p+1之后指向的是pen结构体存储空间后的位置,而不是只加一呢? 因为此处的p+1偏移的是p指向类型大小的偏移量,什么意思呢?p指向的类型为pen类型的结构体, 而pen类型的结构体大小为 len(4字节)加上 data(8个字节),由于此处有内存对齐的情况, 所以实际上pen大小为 4 + 8 + 4(这个4为内存对齐的多余空间,如果再增加一个int类型的变量, pen的大小还是为16)=16字节 所以此处p+1向后偏移了16字节,通过下方地址打印可以详细看出 / strcpy((char)(p + 1), str); //int所占字节数,不同机器不同。一般64位为4字节 printf(“sizeof(int): %ld\n”, sizeof(int)); //上文已说明,16字节 printf(“sizeof(pen): %ld\n”, sizeof(pen)); //起始地址 printf(“start: %p\n\n”, (char)p); //上文已说明,偏移后的地址 printf("(p+1) : %p\n", (char)(p+1)); //偏移后,对应的字符串 printf("(char*)(p+1): %s\n\n", (char*)(p+1)); //结构体变量data的地址 printf("&(p->data): %p\n", &(p->data)); //数据,null,此处为空,故此变量已经被浪费。访问对应字符串数据需要(char *)(p+1) printf(“p->data: %s\n\n”, p->data);}二 柔性数组通过上文我们可以看到,data字段是一个被浪费的指针(8个字节)。并且我们想取到结构体下的字符串变量时需要(char *)(p+1)写这么一串东西,既不好看,也容易出错,那有没有可以直接用p->data取到字符串并且内存是连续的,而且又不浪费data字段呢, 柔性数组 就是用来干这个的。#include <stdio.h>#include <malloc.h>#include <string.h>typedef struct pen{ int len; char data[];//柔性数组}pen;int main(int argc, char **argv){ char str[] = “this is a new string”;//需要填入的字符串 struct pen p = (struct pen)malloc(sizeof(pen) + strlen(str) + 1); p->data= NULL; p->len = strlen(str); strcpy((char *)(p+1), str); printf(“pen->data: %s\n”, p->data);}C99使用不完整类型实现柔性数组成员,在C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组(flexible array)成员(也叫伸缩性数组成员),但结构中的柔性数组成员前面必须至少一个其他成员。柔性数组成员允许结构中包含一个大小可变的数组。柔性数组成员只作为一个符号地址存在,而且必须是结构体的最后一个成员,sizeof 返回的这种结构大小不包括柔性数组的内存 (此段话摘自:https://blog.csdn.net/ce123_z…) ...

February 19, 2019 · 1 min · jiezi

算法与数据结构之递归算法

1.递归算法的核心思想:将问题转化为同类问题的子问题进行求解。2.递归算法的应用:汉诺塔3.问题分析:1.汉诺塔问题: 描述:64个盘子从a移到c,要求一次只能移动一个盘子,并且小盘子在上,大盘子在下。编写功能函数:void hanoi(int n,char a,char b,char c) 含义: n:盘子数量。 a,b,c:三个轴。思路: n=1时,只需要将盘子从a移动到C即可。记作:a->c。 n>1时,进行如下思考:技巧: 我们知道装大象的步骤如下: 1.打开冰箱 2.装大象 3.关冰箱门 观察如下图: 接下来,我们将要打开冰箱 我们发现当n=2时,汉诺塔游戏可以抽象成一个装大象的过程,过程及其简单易懂。 事实上,无论n为多少,我们都可以吧汉诺塔抽象成两个来看,也就是将两个看成一个整体! 而那两个模块(冰箱)我们又可以把它看成一次大象装冰箱的过程,也就是说3个盘子模块会实现2次装大象的过程。 随着盘子数量越来越多,装大象的过程越来越多,我们可以利用函数调用自身函数达到重复循环装的作用,当然你也可以选择for循环,我们这里讨论递归思想。语句等价翻译hanoi(n-1,a,c,b);//该语句代表打开冰箱! a->c;//该语句代表装大象! hanoi(n-1,b,a,c);//该语句代表关冰箱门! 具体分析: hanoi(n-1,a,c,b):表示将冰箱从a这个地方绕过c轴到达b轴这个地方。其中n-1代表冰箱! a->c:表示将a轴上的最后一个模块盘子(最大的,最底部的大象)直接送到c轴上去! hanoi(n-1,b,a,c):表示将b轴上的的冰箱绕过a关到c轴上!以上分析表示了装大象的过程,也是汉诺塔游戏的过程。4.hanoi函数的具体实现:void hanoi(int n, char a,char b,char c)//定义hanoi函数{ if(n==1) printf("%c->%c",a,c);//如果只有一个盘子,也就是说只有大象没有冰箱门的时候,直接把大象装进冰箱里 else { hanoi(n-1,a,c,b);//打开冰箱 printf("%c->%c",a,c);//装大象 hanoi(n-1,b,a,c);//关冰箱门 }}5.主函数的实现:#include<stdio.h>int main(){ int n; printf(“请输入盘子个数:\n); scanf("%d”,&n); hanoi(n,a,b,c);//调用函数 return 0;}6.代码实现:

February 19, 2019 · 1 min · jiezi

Base62x比Base64的编码速度更快吗?

现在几乎所有企事业单位、政府机构、军工系统等的IT生产系统都会用到Base64编码,从RSA安全密钥到管理信息系统登录入口回跳,目前越来越多的IT系统研发者开始使用 Base62x 替换 Base64.-Base62x 提供了一种无符号输出的Base64的编码方案,在许多应用场合其纯字母和数字的输出形式,可以有效的规避因为符号带来的各种负面影响,并能够有效削减或兼容各种Base64的变种形式。借着 Base62x 在 -GitHub 上获得几个赞之后,我们探讨另外一个问题,Base62x 的编码在输出文本形式上略胜一筹,是值得推荐的替代方案,但其编码速度和效率如何?为此我们设计一个评测任务,分别使用Base62x 和 Base64进行20万次编码操作,其中Base62x 使用 PHP的ext module方式实现的 base62x_encode , Base64 也是 PHP内置的 base64_encode , 两种均是使用C语言实现,并通过扩展方式集成在 PHP中,相对而言,可比性较好. 运行主机是在 Windows下虚拟主机运行 OpenSuSE 42,Nginx 1.12, PHP 7.0.通过脚本Command Line调用方式进行20万次编码任务,对比测试数据:base62x 200000 timestart:1513077337.6748 timecost:0.30399990081787base64 200000 timestart:1513077337.9788 timecost:0.16817998886108base62x 200000 timestart:1513077401.2177 timecost:0.29567098617554base64 200000 timestart:1513077401.5134 timecost:0.17081189155579base62x 200000 timestart:1513077424.234 timecost:0.30112099647522base64 200000 timestart:1513077424.5351 timecost:0.1718909740448base62x 200000 timestart:1513077447.9861 timecost:0.29450607299805base64 200000 timestart:1513077448.2806 timecost:0.16546702384949base62x 200000 timestart:1513077470.7367 timecost:0.45493698120117base64 200000 timestart:1513077471.1917 timecost:0.24029588699341运行5次之后,Base62x 和 Base64 其均值分别为,0.330047 和 0.183329 . 由此可见,Base62x 比 Base64 在编码速度上稍慢,20万次操作耗时比值为 1:0.555 , 尽管每次操作其耗时差(7.33589E-07)可以忽略不计,但考察比值,Base62x 慢了大约 44.5%,大致是 Base64 完成两个编码操作, 目前版本的 Base62x 完成一个多一点的编码操作。如果加上在应用层的各种判断,使用 Base62x 替代 Base64 可能是有优势的,比如判断是否包括+,进而转化为空格,是否包括等号等,因为任何一步额外的判断或替换操作,其耗时将可能远超过 Base62x 与 Base64 操作耗时的差值。比如其中一个 URLEncode 的应用场景,在 -github/wadelau/gMIS/comm/ido.js 中( -R/J2SI ):var actx = unescape(tObj.action);actx = actx.replace(‘+‘, ‘ ‘); 取代的改进使用Base62x的方案是:var actx = Base62x.decode(tObj.action); /* no more action needed */从代码层分析耗时差值原因,尽管两者都使用了位操作进行计算,但 Base62x 在单位编码长度上多了数值判断,由此导致其速度下降。Base62x 还是新事物,其代码应该还有可以再改进优化的空间。如果进一步改进优化,Base62x 有可能与 Base64 相同的编码速度吗?有没有可能存在另外一种不需要数值判断,也能够满足与 Base62x 一样无符号输出的64进制编码方案?小结,单就编码速度而言, Base64 方案快,如果加上其他判断与替换操作, Base62x 方案胜出,未来可能会有鱼(无符号输出)和熊掌(速度)兼得的新编码方案出现。-R/p2SQ ...

February 18, 2019 · 1 min · jiezi

python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)

本次为大家推荐的是Python网络安全相关的资源和书籍。其实送过几本Python黑客编程的书,说实话,国内编写或者翻译的这类书籍并没有太好的,说不好,主要原因是都停留在网络编程的简单应用上,入门尚可。下面简单罗列几本,仅供参考。《Python 黑帽子:黑客与渗透测试编程之道》python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)本书由 Immunity 公司的高级安全研究员 Justin Seitz 精心撰写。作者根据自己在安全界,特别是渗透测试领域的几十年经验,向读者介绍了 Python 如何被用在黑客和渗透测试的各个领域,从基本的网络扫描到数据包捕获,从 Web 爬虫到编写 Burp 扩展工具,从编写木马到权限提升等。 作者在本书中的很多实例都非常具有创新和启发意义, 如 HTTP 数据中的图片检测、 基于 GitHub命令进行控制的模块化木马、浏览器的中间人攻击技术、利用 COM 组件自动化技术窃取数据、通过进程监视和代码插入实现权限提升、通过向虚拟机内存快照中插入 shellcode 实现木马驻留和权限提升等。通过对这些技术的学习,读者不仅能掌握各种 Python 库的应用和编程技术,还能拓宽视野,培养和锻炼自己的黑客思维。读者在阅读本书时也完全感觉不到其他一些技术书籍常见的枯燥和乏味。《Python绝技:运用Python成为顶级黑客》python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)这本书翻译过来的名字很牛逼,但是很遗憾,并不能让你达到多高的水平,这本书介绍了很多小技巧。仅此而已。《Python灰帽子:黑客与逆向工程师的Python编程之道》python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)这是我要隆重推荐的第一本书,可能你已经买不到这本书了,但是很容易在网络上找到它的电子版。我是在上大学的时候第一次读的此书的英文版,受益匪浅。 虽然很多内容有点过时,但是方向和思想,基本的编程方法都值得学习。《Python灰帽子:黑客与逆向工程师的Python编程之道》是由知名安全机构Immunity Inc的资深黑帽Justin Seitz先生主笔撰写的一本关于编程语言Python如何被广泛应用于黑客与逆向工程领域的书籍。老牌黑客,同时也是Immunity Inc的创始人兼首席技术执行官(CTO)Dave Aitel为本书担任了技术编辑一职。本书的绝大部分篇幅着眼于黑客技术领域中的两大经久不衰的话题:逆向工程与漏洞挖掘,并向读者呈现了几乎每个逆向工程师或安全研究人员在日常工作中所面临的各种场景,其中包括:如何设计与构建自己的调试工具,如何自动化实现烦琐的逆向分析任务,如何设计与构建自己的fuzzing工具,如何利用fuzzing测试来找出存在于软件产品中的安全漏洞,一些小技巧诸如钩子与注入技术的应用,以及对一些主流Python安全工具如PyDbg、Immunity Debugger、Sulley、IDAPython、PyEmu等的深入介绍。作者借助于如今黑客社区中备受青睐的编程语言Python引领读者构建出精悍的脚本程序来一一应对上述这些问题。出现在本书中的相当一部分Python代码实例借鉴或直接来源于一些优秀的开源安全项目,诸如Pedram Amini的Paimei,由此读者可以领略到安全研究者们是如何将黑客艺术与工程技术优雅融合来解决那些棘手问题的。《Understanding.Network.Hacks.Attack.and.Defense.with.Python》python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)此书值得推荐,是因为它聚焦于网络七层模型的编程和安全问题。书中的示例虽然简单,但是我们完全可以在此基础上扩展出很丰富的内容。 从理解网络协议本身去理解网络安全,理解网络安全编程。全部已经收齐python为什么会作为黑客的首选语言?这几本书给你答案(已集齐)获取方式:qun 前面 984 中间 632 后面 579

February 17, 2019 · 1 min · jiezi

Python:线程之定位与销毁

背景开工前我就觉得有什么不太对劲,感觉要背锅。这可不,上班第三天就捅锅了。我们有个了不起的后台程序,可以动态加载模块,并以线程方式运行,通过这种形式实现插件的功能。而模块更新时候,后台程序自身不会退出,只会将模块对应的线程关闭、更新代码再启动,6 得不行。于是乎我就写了个模块准备大展身手,结果忘记写退出函数了,导致每次更新模块都新创建一个线程,除非重启那个程序,否则那些线程就一直苟活着。这可不行啊,得想个办法清理呀,要不然怕是要炸了。那么怎么清理呢?我能想到的就是两步走:找出需要清理的线程号 tid;销毁它们;找出线程ID和平时的故障排查相似,先通过 ps 命令看看目标进程的线程情况,因为已经是 setName 设置过线程名,所以正常来说应该是看到对应的线程的。 直接用下面代码来模拟这个线程:Python 版本的多线程#coding: utf8import threadingimport osimport timedef tt(): info = threading.currentThread() while True: print ‘pid: ‘, os.getpid() print info.name, info.ident time.sleep(3)t1 = threading.Thread(target=tt)t1.setName(‘OOOOOPPPPP’)t1.setDaemon(True)t1.start()t2 = threading.Thread(target=tt)t2.setName(‘EEEEEEEEE’)t2.setDaemon(True)t2.start()t1.join()t2.join()输出:root@10-46-33-56:# python t.pypid: 5613OOOOOPPPPP 139693508122368pid: 5613EEEEEEEEE 139693497632512…可以看到在 Python 里面输出的线程名就是我们设置的那样,然而 Ps 的结果却是令我怀疑人生:root@10-46-33-56:# ps -Tp 5613 PID SPID TTY TIME CMD 5613 5613 pts/2 00:00:00 python 5613 5614 pts/2 00:00:00 python 5613 5615 pts/2 00:00:00 python正常来说不该是这样呀,我有点迷了,难道我一直都是记错了?用别的语言版本的多线程来测试下:C 版本的多线程#include<stdio.h>#include<sys/syscall.h>#include<sys/prctl.h>#include<pthread.h>void *test(void *name){ pid_t pid, tid; pid = getpid(); tid = syscall(__NR_gettid); char *tname = (char *)name; // 设置线程名字 prctl(PR_SET_NAME, tname); while(1) { printf(“pid: %d, thread_id: %u, t_name: %s\n”, pid, tid, tname); sleep(3); }}int main(){ pthread_t t1, t2; void *ret; pthread_create(&t1, NULL, test, (void *)“Love_test_1”); pthread_create(&t2, NULL, test, (void *)“Love_test_2”); pthread_join(t1, &ret); pthread_join(t2, &ret);}输出:root@10-46-33-56:# gcc t.c -lpthread && ./a.outpid: 5575, thread_id: 5577, t_name: Love_test_2pid: 5575, thread_id: 5576, t_name: Love_test_1pid: 5575, thread_id: 5577, t_name: Love_test_2pid: 5575, thread_id: 5576, t_name: Love_test_1…用 PS 命令再次验证:root@10-46-33-56:# ps -Tp 5575 PID SPID TTY TIME CMD 5575 5575 pts/2 00:00:00 a.out 5575 5576 pts/2 00:00:00 Love_test_1 5575 5577 pts/2 00:00:00 Love_test_2这个才是正确嘛,线程名确实是可以通过 Ps 看出来的嘛!不过为啥 Python 那个看不到呢?既然是通过 setName 设置线程名的,那就看看定义咯:[threading.py]class Thread(_Verbose): … @property def name(self): “““A string used for identification purposes only. It has no semantics. Multiple threads may be given the same name. The initial name is set by the constructor. "”” assert self.__initialized, “Thread.init() not called” return self.__name def setName(self, name): self.name = name …看到这里其实只是在 Thread 对象的属性设置了而已,并没有动到根本,那肯定就是看不到咯这样看起来,我们已经没办法通过 ps 或者 /proc/ 这类手段在外部搜索 python 线程名了,所以我们只能在 Python 内部来解决。于是问题就变成了,怎样在 Python 内部拿到所有正在运行的线程呢?threading.enumerate 可以完美解决这个问题!Why? Because 在下面这个函数的 doc 里面说得很清楚了,返回所有活跃的线程对象,不包括终止和未启动的。[threading.py]def enumerate(): “““Return a list of all Thread objects currently alive. The list includes daemonic threads, dummy thread objects created by current_thread(), and the main thread. It excludes terminated threads and threads that have not yet been started. "”” with _active_limbo_lock: return _active.values() + _limbo.values()因为拿到的是 Thread 的对象,所以我们通过这个能到该线程相关的信息!请看完整代码示例:#coding: utf8import threadingimport osimport timedef get_thread(): pid = os.getpid() while True: ts = threading.enumerate() print ‘——- Running threads On Pid: %d ——-’ % pid for t in ts: print t.name, t.ident print time.sleep(1) def tt(): info = threading.currentThread() pid = os.getpid() while True: print ‘pid: {}, tid: {}, tname: {}’.format(pid, info.name, info.ident) time.sleep(3) returnt1 = threading.Thread(target=tt)t1.setName(‘Thread-test1’)t1.setDaemon(True)t1.start()t2 = threading.Thread(target=tt)t2.setName(‘Thread-test2’)t2.setDaemon(True)t2.start()t3 = threading.Thread(target=get_thread)t3.setName(‘Checker’)t3.setDaemon(True)t3.start()t1.join()t2.join()t3.join()输出:root@10-46-33-56:# python t_show.pypid: 6258, tid: Thread-test1, tname: 139907597162240pid: 6258, tid: Thread-test2, tname: 139907586672384——- Running threads On Pid: 6258 ——-MainThread 139907616806656Thread-test1 139907597162240Checker 139907576182528Thread-test2 139907586672384——- Running threads On Pid: 6258 ——-MainThread 139907616806656Thread-test1 139907597162240Checker 139907576182528Thread-test2 139907586672384——- Running threads On Pid: 6258 ——-MainThread 139907616806656Thread-test1 139907597162240Checker 139907576182528Thread-test2 139907586672384——- Running threads On Pid: 6258 ——-MainThread 139907616806656Checker 139907576182528…代码看起来有点长,但是逻辑相当简单,Thread-test1 和 Thread-test2 都是打印出当前的 pid、线程 id 和 线程名字,然后 3s 后退出,这个是想模拟线程正常退出。而 Checker 线程则是每秒通过 threading.enumerate 输出当前进程内所有活跃的线程。可以明显看到一开始是可以看到 Thread-test1 和 Thread-test2的信息,当它俩退出之后就只剩下 MainThread 和 Checker 自身而已了。销毁指定线程既然能拿到名字和线程 id,那我们也就能干掉指定的线程了!假设现在 Thread-test2 已经黑化,发疯了,我们需要制止它,那我们就可以通过这种方式解决了:在上面的代码基础上,增加和补上下列代码:def _async_raise(tid, exctype): “““raises the exception, performs cleanup if needed””” tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError(“invalid thread id”) elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError(“PyThreadState_SetAsyncExc failed”)def stop_thread(thread): _async_raise(thread.ident, SystemExit)def get_thread(): pid = os.getpid() while True: ts = threading.enumerate() print ‘——- Running threads On Pid: %d ——-’ % pid for t in ts: print t.name, t.ident, t.is_alive() if t.name == ‘Thread-test2’: print ‘I am go dying! Please take care of yourself and drink more hot water!’ stop_thread(t) print time.sleep(1)输出root@10-46-33-56:# python t_show.pypid: 6362, tid: 139901682108160, tname: Thread-test1pid: 6362, tid: 139901671618304, tname: Thread-test2——- Running threads On Pid: 6362 ——-MainThread 139901706389248 TrueThread-test1 139901682108160 TrueChecker 139901661128448 TrueThread-test2 139901671618304 TrueThread-test2: I am go dying. Please take care of yourself and drink more hot water!——- Running threads On Pid: 6362 ——-MainThread 139901706389248 TrueThread-test1 139901682108160 TrueChecker 139901661128448 TrueThread-test2 139901671618304 TrueThread-test2: I am go dying. Please take care of yourself and drink more hot water!pid: 6362, tid: 139901682108160, tname: Thread-test1——- Running threads On Pid: 6362 ——-MainThread 139901706389248 TrueThread-test1 139901682108160 TrueChecker 139901661128448 True// Thread-test2 已经不在了一顿操作下来,虽然我们这样对待 Thread-test2,但它还是关心着我们:多喝热水,PS: 热水虽好,八杯足矣,请勿贪杯哦。书回正传,上述的方法是极为粗暴的,为什么这么说呢?因为它的原理是:利用 Python 内置的 API,触发指定线程的异常,让其可以自动退出;为什么停止线程这么难多线程本身设计就是在进程下的协作并发,是调度的最小单元,线程间分食着进程的资源,所以会有许多锁机制和状态控制。如果使用强制手段干掉线程,那么很大几率出现意想不到的bug。 而且最重要的锁资源释放可能也会出现意想不到问题。我们甚至也无法通过信号杀死进程那样直接杀线程,因为 kill 只有对付进程才能达到我们的预期,而对付线程明显不可以,不管杀哪个线程,整个进程都会退出!而因为有 GIL,使得很多童鞋都觉得 Python 的线程是Python 自行实现出来的,并非实际存在,Python 应该可以直接销毁吧?然而事实上 Python 的线程都是货真价实的线程!什么意思呢?Python 的线程是操作系统通过 pthread 创建的原生线程。Python 只是通过 GIL 来约束这些线程,来决定什么时候开始调度,比方说运行了多少个指令就交出 GIL,至于谁夺得花魁,得听操作系统的。如果是单纯的线程,其实系统是有办法终止的,比如: pthread_exit,pthread_kill 或 pthread_cancel, 详情可看:https://www.cnblogs.com/Creat…很可惜的是: Python 层面并没有这些方法的封装!我的天,好气!可能人家觉得,线程就该温柔对待吧。如何温柔退出线程想要温柔退出线程,其实差不多就是一句废话了要么运行完退出,要么设置标志位,时常检查标记位,该退出的就退出咯。扩展《如何正确的终止正在运行的子线程》:https://www.cnblogs.com/Creat…《不要粗暴的销毁python线程》:http://xiaorui.cc/2017/02/22/…欢迎各位大神指点交流, QQ讨论群: 258498217 转载请注明来源: https://segmentfault.com/a/11… ...

February 16, 2019 · 3 min · jiezi

DPDK 源码的不完全笔记(一) 初识DPDK

写在前面本系列记录了作者在项目过程中由于好奇心驱使而了解到的部分DPDK实现细节。比较适合有同样好奇心的DPDK的初学者,通过本系列文章您可以学习到DPDK的整体工作原理以及部分实现细节您不能学习到应用DPDK进行性能调优如果对DPDK的起源不是很清楚的话,可以先浏览下 绝对干货!初学者也能看懂的DPDK解析,重点就是Linux + x86网络IO瓶颈 这部分,总结一句话就是Linux内核协议栈太慢了,为了突破这种性能瓶颈,DPDK的方案是绕过(bypass)内核,直接从网卡把数据抓到用户空间。一些基本的概念EAL首先必须明白的一点就是,DPDK是以若干个lib的形式提供给应用链接使用,其中最终要的一个lib就是EAL了,EAL的全称是(Environment Abstraction Layer, 环境抽象层),它负责为应用间接访问底层的资源,比如内存空间、线程、设备、定时器等。如果把我们的应用比作一个豪宅的主人的话,EAL就是这个豪宅的管家。lcore & socket这两个概念在 DPDK的代码中随处可见,注意这里的 socket 不是网络编程里面的那一套东西,而是CPU相关的东西。具体的概念可以参看Differences between physical CPU vs logical CPU vs Core vs Thread vs Socket 或者其翻译版本physical CPU vs logical CPU vs Core vs Thread vs Socket(翻译)。对我们来说,只要知道可以DPDK可以运行在多个lcore上就足够了.DPDK 如何知道有多少个lcore呢 ? 在启动时解析文件系统中的特定文件就可以了, 参考函数eal_cpu_detectedDPDK的运行形式大部分DPDK的代码是以lib的形式运行在用户应用的进程上下文.为了达到更高的性能。应用通常都会多进程或者多线程的形式运行在不同的lcore上多线程的场景:多进程的场景:多进程的场景下,多个应用实例如何保证关键信息(比如内存资源)的一致性呢? 答案是不同进程将公共的数据mmap同一个文件,这样任何一个进程对数据的修改都可以影响到其他进程。Primary & Secondary多进程场景下,进程有两种角色Primary或者Secondary,正如其名字,Primary进程可以create 资源,而Secondary进程只能 attach已存在的资源。一山不容二虎,一个多进程的应用,有且只有一个Primary进程,其余都是Secondary进程。应用可以通过命令行参数 –proc-type 来指定应用类型。DPDK的入口如同main函数在应用程序中的地位,rte_eal_init函数便是DPDK梦开始的地方(其实前面的图已经画出来了!),我们来看看它做了什么事。/* Launch threads, called at application init(). */intrte_eal_init(int argc, char *argv){ thread_id = pthread_self(); rte_eal_cpu_init(); eal_parse_args(argc, argv); rte_config_init(); rte_eal_intr_init(); rte_mp_channel_init(); rte_eal_memzone_init(); rte_eal_memory_init(); rte_eal_malloc_heap_init() eal_thread_init_master(rte_config.master_lcore); RTE_LCORE_FOREACH_SLAVE(i) { / create a thread for each lcore / ret = pthread_create(&lcore_config[i].thread_id, NULL, eal_thread_loop, NULL); ….. } / * Launch a dummy function on all slave lcores, so that master lcore * knows they are all ready when this function returns. */ rte_eal_mp_remote_launch(sync_func, NULL, SKIP_MASTER); rte_eal_mp_wait_lcore(); ……} 总结起来就是检测lcore解析用户的命令行参数内存资源等子模块初始化在其他lcore上启动线程 ...

February 14, 2019 · 1 min · jiezi

PAT A1107

并查集的应用,但是一上来有点蒙蔽,因为这次多了一个媒介,通过活动来判断人员是否在一个集合;有一个示例思路:构建活动的集合,首次发现在该活动的人员,将对应活动索引的内容设置为该人员编号;如果后面输入还有该活动,就将具有该活动的人员和数组内的人员进行并查集合并;大致代码如下所示:#include<stdio.h>#include<stdlib.h>#include<algorithm>using namespace std;const int N=1010;int course[N]={0};int father[N];int isRoot[N]={0};void init(int n){ for(int i=1;i<=n;i++){ father[i]=i; isRoot[i]=0; }}int findfather(int x){ int a=x; while(x!=father[x]){ x=father[x]; } //进行并查集路径的压缩 while(a!=father[a]){ int z=a; a=father[a]; father[z]=x; } return x;}void Union(int a,int b){ int faA=findfather(a); int faB=findfather(b); if(faA!=faB) father[faA]=faB;}bool cmp(int a,int b){ return a>b;}int n,h;int main(){ scanf("%d",&n); init(n); for(int i=1;i<=n;i++){ int num; scanf("%d:",&num); for(int j=0;j<num;j++){ scanf("%d",&h); if(course[h]==0){ course[h]=i; } Union(i,findfather(course[h])); } } for(int i=1;i<=n;i++){ isRoot[findfather(i)]++; } int ans=0; for(int i=1;i<=n;i++){ if(isRoot[i]!=0){ ans++; } } printf("%d\n",ans); sort(isRoot+1,isRoot+n+1,cmp); for(int i=1;i<=ans;i++){ printf("%d",isRoot[i]); if(i<ans) printf(" “); } system(“pause”); return 0;} ...

February 11, 2019 · 1 min · jiezi

记录一次过程(1):Building Mosquitto from the git repository

MosquittoMosquittto是针对MQTT 3.1版本和3.1.1版本的一个开源实现的服务器。它包含C和C++的客户端库,以及用于publish/subscribe的实用程序:mosquitto_pub/mosquitto_sub。Mosquitto项目对于如何Building from source描述并不足够清晰,但其实步骤也还算简单。步骤1.cmake使用命令cmake -DWITH_TLS_PSK=OFF -DWITH_TLS=OFF得到– Configuring done– Generating done这两行中间有warning,由于我只是需要搭建一个简单的环境做简单地测试,就没有管warning。2.make使用命令make得到[100%] Linking C executable mosquitto[100%] Built target mosquitto3.sudo make install使用命令sudo make install报错。报错信息如下:Install the project…– Install configuration: “”– Up-to-date: /usr/local/etc/mosquitto/mosquitto.conf– Up-to-date: /usr/local/etc/mosquitto/aclfile.example– Up-to-date: /usr/local/etc/mosquitto/pskfile.example– Up-to-date: /usr/local/etc/mosquitto/pwfile.example– Up-to-date: /usr/local/lib/pkgconfig/libmosquitto.pc– Up-to-date: /usr/local/lib/pkgconfig/libmosquittopp.pc– Installing: /usr/local/lib/libmosquitto.1.5.5.dylib– Up-to-date: /usr/local/lib/libmosquitto.1.dylib– Up-to-date: /usr/local/lib/libmosquitto.dylib– Up-to-date: /usr/local/include/mosquitto.h– Installing: /usr/local/lib/libmosquittopp.1.5.5.dylib– Up-to-date: /usr/local/lib/libmosquittopp.1.dylib– Up-to-date: /usr/local/lib/libmosquittopp.dylib– Up-to-date: /usr/local/include/mosquittopp.h– Installing: /usr/local/bin/mosquitto_pub– Installing: /usr/local/bin/mosquitto_sub– Installing: /usr/local/sbin/mosquitto– Up-to-date: /usr/local/include/mosquitto_broker.h– Up-to-date: /usr/local/include/mosquitto_plugin.hCMake Error at man/cmake_install.cmake:36 (file): file INSTALL cannot find “/Users/xxxx/Desktop/iMac-mosquitto/mosquitto-master/man/mosquitto_passwd.1”.Call Stack (most recent call first): cmake_install.cmake:57 (include)make: * [install] Error 1报错之后首先检查报错信息,google之后仍然有解决方面的困难。这时我们注意到上面加粗的三行,切换到安装目录 /usr/local/include/ 后运行命令 mosquitto发现可以运行,但是在安装目录外就不可以运行。这时候我们的下一步就是配置环境变量,从而使mosquitto的运行不受当前目录的限制。4.添加环境变量环境变量的作用是:可以在操作系统的各个目录下,都能访问到需要的工具目录内的内容。我参考的是Mac OS X 系统的环境变量配置分别将/usr/local/bin/以及/usr/local/sbin/加入环境变量之后(我将路径加入了用户级的~/.bash_profile),就可以运行mosquitto了。但是还有一个烦人的小问题,就是Mac每次都要执行source /.bash_profile,配置的环境变量才生效。解决方法是:在/.zshrc文件最后,增加一行: source ~/.bash_profile5.运行测试先开一个terminal,输入命令mosquitto。打开第二个terminal,输入命令mosquitto_sub -h localhost -t test。打开第三个terminal,输入命令mosquitto_pub -h localhost -t test -m “hello world”。发现在第二个terminal上出现了hello world字符串,即成功。6.思考几个问题,后续解决。直接用cmake .是会由于OpenSSL的路径问题报错的,OpenSSL是干什么用的?我没有去解决OpenSSL的路径问题,而是忽略了它,关闭了TLS直接cmake的。TLS又是什么?起到了什么作用?程序运行起来经过了哪些步骤?一个大型项目是如何安装运行起来的?为什么安装mosquitto的步骤是cmake->make->sudo make install? ...

February 11, 2019 · 1 min · jiezi

Kotlin/Native尝试

Kotlin/Native尝试在官网看到Kotlin/Native已经达到1.0 Beta版于是就去尝试了一下,结果发现坑还是挺多的。首先Kotlin/JVM很多库是用不了的,这个已经猜到了。官网说已经预先导入了 POSIX、 gzip、 OpenGL、 Metal、 Foundation 等很多其他的库,然后我就尝试了下基本的文件读写。和C还是有一点的差别的。如下:fun hello(): String = “Hello, Kotlin/Native!“fun letter() = “abcdefghigklmnopqrstuvwxyz"fun main(args: Array<String>) { val file = fopen(“data.txt”, “w”) fprintf(file, “%s”, hello()) fprintf(file, “%s”, “\n”) fprintf(file, “%s”, letter()) fclose(file) println(“write finished”) val filer = fopen(“data.txt”, “r”) val buf = ByteArray(255)// fscanf(filer, “%s”, buf.pin().addressOf(0)) fgets(buf.pin().addressOf(0), 255, filer) fclose(filer) print(buf.stringFromUtf8()) buf.pin().unpin() println(“read finished”) system(“pause”)}运行结果如下> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found> Task :runProgram FAILED令人郁闷的是提示C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found,虽然调用了buf.pin().unpin(),但依旧提示内存泄漏,也没有异常退出啊。如果是改成如下方式就不会提示错误了:fun hello(): String = “Hello, Kotlin/Native!“fun letter() = “abcdefghigklmnopqrstuvwxyz"fun main(args: Array<String>) { val file = fopen(“data.txt”, “w”) fprintf(file, “%s”, hello()) fprintf(file, “%s”, “\n”) fprintf(file, “%s”, letter()) fclose(file) println(“write finished”) val filer = fopen(“data.txt”, “r”) val buf = ByteArray(255)// fscanf(filer, “%s”, buf.pin().addressOf(0))// fgets(buf.pin().addressOf(0), 255, filer)// fclose(filer)// print(buf.stringFromUtf8())// buf.pin().unpin() buf.usePinned { fgets(it.addressOf(0), 255, filer) fclose(filer) print(buf.stringFromUtf8()) } println(“read finished”) system(“pause”)}结果如下:> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . BUILD SUCCESSFUL in 9s另外吐槽下,这么几行代码就要9s,是不是太慢了。随后又试了下开启pthread线程,但是pthread_create函数的第一个参数th: kotlinx.cinterop.CValuesRef<platform.posix.pthread_tVar>,CValuesRef类型的变量怎么获得一直无解,难道只能通过继承获得?然后我在写文章的时候又发现只要这样写就可以了???fun main(args: Array<String>) { pthread_create(null, null, test(), null)}typealias func = kotlinx.cinterop.CPointer<kotlinx.cinterop.CFunction<(kotlinx.cinterop.COpaquePointer?) -> kotlinx.cinterop.COpaquePointer?>>?fun test(): func { return staticCFunction<kotlinx.cinterop.COpaquePointer?, kotlinx.cinterop.COpaquePointer?> { println(“run test”) it }}结果如下:> Task :runProgramrun testBUILD SUCCESSFUL in 8s> Task :runProgramwrite finishedHello, Kotlin/Native!read finishedPress any key to continue . . . C:\BuildAgent\work\4d622a065c544371\runtime\src\main\cpp\Memory.cpp:1150: runtime assert: Memory leaks found ...

February 5, 2019 · 1 min · jiezi

C语言实现一个简易的Hash table(7)

上一章我们讲了如何根据需要动态设置hash表的大小,在第四章中,我们使用了双重哈希来解决hash表的碰撞,其实解决方法有很多,这一章我们来介绍下其他方法。本章将介绍两种解决hash表碰撞的方法:拉链法开放地址法拉链法使用拉链法,每一个bucket都会包含一个链接表,当发生碰撞时,就会将该记录插入在该位置的链接表后面,步骤如下:插入时:通过hash函数获取到要插入的位置,如果该位置是空的,就直接插入,如果该位置不是空的,就插入在链接表的后面搜索时:通过hash函数获取到key对应的位置,遍历链接表,判断key是不是搜索的key,如果是,则返回value,否则返回NULL删除时:通过hash函数获取到key对应的位置,遍历链接表,找到需要删除的key,如果找到,则将该key对应的记录从链接表中删除,如果链接表中只有一条记录,则将该位置置为NULL拉链法的优点是实现起来简单,但是空间利用率低。每个记录必须存储指向链接表中下一个记录的指针,如果没有记录,则指向NULL,这种方法会浪费一些空间来存储额外的指针。开放地址法开放地址法能解决拉链法空间利用率低的问题,发生碰撞时,碰撞的记录将放置在hash表中的其他bucket中,存放的位置是根据预先确定的规则选择的,以便在搜索记录时可以重复该规则,有如下几种规则:线性探查当发生碰撞时,就会递增索引,将记录插入在下一个可用的索引中,方法如下:插入时:通过hash函数找到插入的位置的索引,如果这个位置是空的,直接插入,如果不为空,就递增索引,直到找到索引指向的位置是空的为止,然后执行插入搜索时:通过hash函数找到搜索的记录的索引,每次递增索引,并比较索引指向的值是否是要搜索的值,如果索引指向的是空,则返回NULL删除时:通过hash函数找到删除的记录的索引,每次递增索引,直到找到要删除的那个key后执行删除线性探测提供了良好的缓存性能,但是存在碰撞后遍历次数多的问题。将发生碰撞的key放入下一个可用的bucket中可能导致后面插入记录也要往后插,就需要多次迭代。二次探查二次探查法和先行探查类似,不同的是,发生碰撞后,我们会将记录插入在如下的序列中:i, i + 1, i + 4, i + 9, i + 16, …,i代表通过hash函数获取到的索引,具体步骤如下:插入时:通过hash函数找到插入的索引,通过遍历上面的序列直到找到一个空的或已被删除的索引位置,执行插入搜索时:通过hash函数找到key的索引,遍历上面的序列,将序列上的key与搜索的key对比,如果相等,则返回value,否则返回NULL删除时:因为我们无法判断要删除的项是不是碰撞链上的,所以我们不能直接删除该条记录,只能把它标记为已删除二次探查法减少发生碰撞后遍历的次数,并且仍然提供了不错的缓存性能。双重hash双重hash旨在解决碰撞后遍历次数多的问题。使用两次hash函数为插入的记录选择新的索引,这个索引会均匀的分布在整个表中,该方法虽然解决了上述问题,但也失去了缓存特性,双重hash是实际项目中常见的冲突管理方法,也是我们在本教程中实现的方法。上一章:设置hash表大小

February 3, 2019 · 1 min · jiezi

二进制状态码

我们知道计算机中数据都是用二进制数存储。二进制数是一系列0和1的组合,长整型64位,最短的字节型也有8位。其中每一位0和1都可以看做一种状态的开和关,所以就有了这样的一种状态码存储方式:把同一对象的多种状态按位组合到一个整数中。例如我们最最常见的 *nix 文件权限:第9位第8位第7位第6位第5位第4位第3位第2位第1位第0位是否目录所有者读权限所有者写权限所有者执行权限组读权限组写权限组执行权限其余用户读权限其余用户写权限其余用户执行权限0111101101那么这一组状态在程序中表示为:0b0111101101,即八进制的 0o755,十进制的 493。二进制状态码存储的主要好处是节省存储空间,相对于键值对(对象)存储而言可读性较差(当然文件权限这种另说)。这种存储方式仅适用于“一个对象有多种状态,每种状态仅有两种情况”这一情形,请不要对一种状态多种情况的情形使用二进制状态码存储方式,更不要出现十进制的 0 1 10 这种状态码,很蠢。。。使用位运算操作状态码基于这种存储方式,也衍生了一些操作状态码的方式:判断第 x 位状态是否开启(x 以 0 开始,下同):status & (1 << x) == 0打开第 x 位status |= 1 << x关闭第 x 位status &= ~(1 << x)编程语言支持某些编程语言提供了对二进制状态码的一些原生支持。C/C++ 提供了 位域,以及专门的模板库 bitset 用于简化位运算操作。C# 则提供了 Flags 特性标记某个枚举被视作位域另外很重要一点,JavaScript 虽然也支持位运算,但由于 JavaScript 中的 number 类型都是双精度浮点数,在做位运算时会先将数值截断至 32 位长度。例如很著名的数字转整数bug:10000000000 | 0 => 1410065408。所以注意如果后端返回二进制状态码让前端判断,确保后端使用 uint32_t 存储完

February 1, 2019 · 1 min · jiezi

【Redis源码研究】Redis的RESP协议

作者:张仕华resp协议redis客户端和服务端交互使用的是redis作者制定的一个协议,叫resp(REdis Serialization Protocol)。具体分如下几个层次基于tcp请求响应模式,但在两种情况下不再是简单的请求和响应模式(下文介绍)支持五种类型的数据,分别是简单字符串,错误,整型,bulk strings ,数组客户端发给服务端的命令都会序列化为array,而服务端返回给客户端的可以为如上任意一种类型,各简单举例如下简单字符串,第一个byte为 ‘+’,例如 “+OKrn"错误,第一个byte为’-’,例如 “-Error messagern"整型, 第一个byte为’:",例如 “:10rn"bulk strings,第一个byte为”&dollar;",例如 “&dollar;6rnfoobarrn”(此处为美元符号,没有前边的反斜杠)数组,第一个byte为’’,例如"2rn$3\r\nfoo\r\n$3rnbarrn"具体介绍参考:https://redis.io/topics/protocolsub pattern请求响应模式有两种特殊情况1.pipeline模式,客户端同时发送多条命令到服务端2.sub pattern:当客户端执行subscribe命令后,不再要求客户端发送命令,当有其他客户端在订阅渠道上publish消息后,服务端会主动push信息到客户端我们拿redis-cli客户端试一下执行subscribe127.0.0.1:6379> subscribe fooReading messages… (press Ctrl-C to quit)1) “subscribe"2) “foo"3) (integer) 1可以看到,redis-cli会阻塞,当另起一个客户端,publish消息后,会收到该消息并打印~$redis-cli127.0.0.1:6379> publish foo bar(integer) 1~$redis-cli127.0.0.1:6379> subscribe fooReading messages… (press Ctrl-C to quit)1) “subscribe"2) “foo"3) (integer) 11) “message"2) “foo"3) “bar"直观感觉是服务端阻塞了,没有返回数据给客户端。但看redis源码,实际服务端并没有阻塞,并且可以在连接上继续接收并处理命令void subscribeCommand(client *c) { int j; //将订阅的渠道加入相应结构体并直接返回 for (j = 1; j < c->argc; j++) pubsubSubscribeChannel(c,c->argv[j]); //将客户端置CLINET_PUBSUB标记 c->flags |= CLIENT_PUBSUB;}当客户端置了CLINET_PUBSUB标记后,命令处理会做如下特殊逻辑int processCommand(client *c) { … //当置CLIENT_PUBSUB标记后,只有ping/subscribe/unsubscribe/psubscribe/punsubscribe命令能够执行 if (c->flags & CLIENT_PUBSUB && c->cmd->proc != pingCommand && c->cmd->proc != subscribeCommand && c->cmd->proc != unsubscribeCommand && c->cmd->proc != psubscribeCommand && c->cmd->proc != punsubscribeCommand) { addReplyError(c,“only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context”); return C_OK; } …}godis-cli如上文所示,当客户端执行subscribe命令后,实际上是可以继续订阅或者取消订阅渠道,并且可以执行ping命令。redis-cli客户端实际上是自己阻塞了,如下代码:if (config.pubsub_mode) { if (config.output != OUTPUT_RAW) printf(“Reading messages… (press Ctrl-C to quit)\n”); //进入死循环,一直等待服务端发送消息 while (1) { if (cliReadReply(output_raw) != REDIS_OK) exit(1); }}那么,我们可以拿go写一个不阻塞的版本,并且可以测试redis的subscribe 模式。效果如下>bogon:godis-cli $ go run godis-cli.go> set k1 v1 OK> get k1v1> subscribe foosubscribe foo 1[sub]>subscribe foo1//sub模式下可以继续订阅其他渠道subscribe foo1 2[sub]> unsubscribe foo1//取消订阅unsubscribe foo1 1[sub]> ping//sub模式也可以执行pingpong[sub]> get k1 //sub模式下不能执行get命令Redis Error: only (P)SUBSCRIBE / (P)UNSUBSCRIBE / PING / QUIT allowed in this context[sub]> exitexit sub pattern….>get k1//退出sub模式后可以继续正常执行get命令v1> exit由于godis-cli直接实现了RESP协议,虽然只是为了观察subscribe pattern的效果,但实际上可以支持所有redis命令的执行具体代码地址见:https://github.com/erpeng/god… ...

February 1, 2019 · 1 min · jiezi

C语言实现一个简易的Hash table(6)

上一章中,我们实现了Hash表中的插入、搜索和删除接口,我们在初始化hash表时固定了大小为53,为了方便扩展,本章将介绍如何修改hash表的大小。设置Hash表大小现在,我们的hash表是固定大小(53)的,当插入越来越多数据时,我们的hash表就会被插满,这个问题有两个原因:哈希表的性能随着高冲突率而降低我们的’hash表’只能存储固定数量的记录,如果我们存储更多,将无法插入数据为了减少hash表被插满的情况发生,当插入很多数据时,我们可以增大hash表的大小,hash表中的count属性代表已经插入的数据条数,在每次插入和删除时,我们计算表的“负载”,或插入的数量和总的大小的比率,如果它高于或低于某些值,我们会减小或扩大hash表的大小。我们定义如下规则:如果负载>0.7,就扩大如果负载<0.1,就缩小要调整大小,我们创建一个大约是当前大小的一半或两倍的新哈希表,并将所有未删除的项插入其中。我们的新hash表大小应该是大约是当前大小的两倍或一半的素数,找到新的hash表大小并非易事。为了确定hash表的大小,我们现设置一个最基本的大小,然后将实际大小定义为大于基本大小的第一个素数。扩大时,我们先将基本大小加倍,找到第一个更大的素数,然后作为hash表的大小,缩小时,我们将大小减半并找到下一个更大的素数。我们先从基本大小50开始,我们使用最简单粗暴的方法通过检查每个连续数是否为素数来查找下一个素数。这个简单粗暴的方法看起来不是很理想,但是我们实际需要检查的值很少,并且花费的时间超过了重新散列表中每个项目所花费的时间。首先,我们先定义一个函数用来找到下一个素数,prime.h和prime.c的内容如下:// prime.hint is_prime(const int x);int next_prime(int x);// prime.c#include <math.h>#include “prime.h”/* * Return whether x is prime or not * * Returns: * 1 - prime * 0 - not prime * -1 - undefined (i.e. x < 2) /int is_prime(const int x) { if (x < 2) { return -1; } if (x < 4) { return 1; } if ((x % 2) == 0) { return 0; } for (int i = 3; i <= floor(sqrt((double) x)); i += 2) { if ((x % i) == 0) { return 0; } } return 1;}/ * Return the next prime after x, or x if x is prime /int next_prime(int x) { while (is_prime(x) != 1) { x++; } return x;}下一步,我们需要修改ht_new函数,使之可以在创建hash表时指定大小,为此我们要创建一个新的函数ht_new_sized,在ht_new中我们调用ht_new_sized并给我们的hash表一个默认大小:// hash_table.cstatic ht_hash_table ht_new_sized(const int base_size) { ht_hash_table* ht = xmalloc(sizeof(ht_hash_table)); ht->base_size = base_size; ht->size = next_prime(ht->base_size); ht->count = 0; ht->items = xcalloc((size_t)ht->size, sizeof(ht_item*)); return ht;}ht_hash_table* ht_new() { return ht_new_sized(HT_INITIAL_BASE_SIZE);}现在一切准备就绪。在我们的设置hash表大小函数中,我们需要检查以确保我们没有将哈希表的大小减小到最小值以下,然后,我们初始化一个所需大小的新hash表,原表中所有非NULL或者未被删除的都会插入到新hash表中,然后我们在删除旧的hash表之前将属性赋值给新的hash表。// hash_table.cstatic void ht_resize(ht_hash_table* ht, const int base_size) { if (base_size < HT_INITIAL_BASE_SIZE) { return; } ht_hash_table* new_ht = ht_new_sized(base_size); for (int i = 0; i < ht->size; i++) { ht_item* item = ht->items[I]; if (item != NULL && item != &HT_DELETED_ITEM) { ht_insert(new_ht, item->key, item->value); } } ht->base_size = new_ht->base_size; ht->count = new_ht->count; // To delete new_ht, we give it ht’s size and items const int tmp_size = ht->size; ht->size = new_ht->size; new_ht->size = tmp_size; ht_item** tmp_items = ht->items; ht->items = new_ht->items; new_ht->items = tmp_items; ht_del_hash_table(new_ht);}为了简化设置大小,我们定义了两个函数:// hash_table.cstatic void ht_resize_up(ht_hash_table* ht) { const int new_size = ht->base_size * 2; ht_resize(ht, new_size);}static void ht_resize_down(ht_hash_table* ht) { const int new_size = ht->base_size / 2; ht_resize(ht, new_size);}要执行调整大小,我们先检查插入和删除时hash表上的负载。 如果它高于或低于0.7和0.1的预定义限制,我们分别调高或调低。为了避免进行浮点运算,我们将计数乘以100,并检查它是高于还是低于70或10:// hash_table.cvoid ht_insert(ht_hash_table* ht, const char* key, const char* value) { const int load = ht->count * 100 / ht->size; if (load > 70) { ht_resize_up(ht); } // …}void ht_delete(ht_hash_table* ht, const char* key) { const int load = ht->count * 100 / ht->size; if (load < 10) { ht_resize_down(ht); } // …}上一章:实现接口下一章:附录:替代碰撞处理 ...

January 27, 2019 · 2 min · jiezi

身份证号校验公式

原理:身份证号的最后一位是根据前 17 位数字计算出来的,具有唯一性。计算方式:将身份证号的 { [第 1 个数字 (2^17/11) 的余数] + [第 2 个数字 (2^16/11)的余数] + …+ [第 17 个数字 * (2^1/11)的余数] } ,将所得的数除以 11 后,得到的余数按照余数转化后01102X39485766758493102依次对应。最后的结果就是第 18 位身份证号校验位。公式代码:Excel公式=LOOKUP(MOD(MID(B3,1,1)*MOD(2^17,11)+MID(B3,2,1)*MOD(2^16,11)+MID(B3,3,1)*MOD(2^15,11)+MID(B3,4,1)*MOD(2^14,11)+MID(B3,5,1)*MOD(2^13,11)+MID(B3,6,1)*MOD(2^12,11)+MID(B3,7,1)*MOD(2^11,11)+MID(B3,8,1)*MOD(2^10,11)+MID(B3,9,1)*MOD(2^9,11)+MID(B3,10,1)*MOD(2^8,11)+MID(B3,11,1)*MOD(2^7,11)+MID(B3,12,1)*MOD(2^6,11)+MID(B3,13,1)*MOD(2^5,11)+MID(B3,14,1)*MOD(2^4,11)+MID(B3,15,1)*MOD(2^3,11)+MID(B3,16,1)*MOD(2^2,11)+MID(B3,17,1)*MOD(2^1,11),11),{0;1;2;3;4;5;6;7;8;9;10},{1;0;“X”;9;8;7;6;5;4;3;2})C语言代码#include<stdio.h>#include<stdlib.h>#include<math.h>int reIdNum(char id[]){ char check = ‘1’; int i = 16, sum = 0, result; for(i = 16; i >= 0; i–){ sum += ( (id[16-i] - ‘0’) * (int(pow(2,(i+1))) % 11) ); } switch (sum % 11){ case 0 : check = ‘1’; break; case 1 : check = ‘2’; break; case 2 : check = ‘X’; break; case 3 : check = ‘9’; break; case 4 : check = ‘8’; break; case 5 : check = ‘7’; break; case 6 : check = ‘6’; break; case 7 : check = ‘5’; break; case 8 : check = ‘4’; break; case 9 : check = ‘3’; break; case 10 : check = ‘2’; break; } if(id[17] == check){ result = 1; }else{ result = 0; } return (result);}int main(){ char id[19]; puts(“Please enter IdNumber:”); scanf("%s", id); if(reIdNum(id)){ puts(“The IdNumber is ture.”); }else{ puts(“The IdNumber is false.”); } system(“pause”); return (0);} ...

January 27, 2019 · 1 min · jiezi

C语言权威指南和书单 - 专家级别

注: 点击标题即可下载1. Advanced Programming in the UNIX Environment, 3rd Edition2. Essential C3. Computer Systems(Second Edition) A Programmer’s Perspective更多免费电子书资源搜索 | Maning参考The Definitive C Book Guide and List

January 25, 2019 · 1 min · jiezi

C语言权威指南和书单 - 中等级别

注:点击标题免费下载电子书1. Object-oriented Programming with ANSI-C2. C Interfaces and Implementations3. 21st Century C: C Tips from the New School4. Algorithms in C (Computer Science Series)5. Pointers on CProblem Solving and Program Design in CModern C更多免费电子书资源搜索 | Maning参考The Definitive C Book Guide and List

January 24, 2019 · 1 min · jiezi

C语言权威指南和书单 - 适用于所有级别

注:点击标题免费下载电子书所有级别1. The C Programming Language (2nd Edition)2. C: A Reference Manual (5th Edition)3. C语言常见问题4. C标准5. The new C standard - an annotated reference6. Rationale for C99 Standard7. The new C standard - an annotated reference更多免费电子书资源搜索 | Maning参考The Definitive C Book Guide and List

January 23, 2019 · 1 min · jiezi

C语言权威指南和书单 - 初学者

注:点击标题免费下载电子书1. C Primer Plus (5th Edition)2. A Book on CC Programming: A Modern Approach (2nd Edition)3. The C Book4. Beginning C (5th Edition)5. Sams Teach Yourself C in 21 Days6. Head First C7. C How to Program更多免费电子书资源搜索 | Maning参考The Definitive C Book Guide and List

January 23, 2019 · 1 min · jiezi

一步步进行ffmpeg的C语言音视频编程

本文以ffmpeg的C语言编程过程,讲述简单的音视频转码过程更多内容可以访问我的博客前言本文以 ffmpeg 工具,讲述如何认识音视频编程,你可以了解到常见视频格式的大概样子,一步步学会如何使用 ffmpeg 的 C 语言 API本文重于动手实践,代码仓库:mpegUtil笔者的开发环境:Arch Linux 4.19.12, ffmpeg version n4.1解码过程总览以下是解码流程图,逆向即是编码流程本文是音视频编程入门篇,先略过传输协议层,主要讲格式层与编解码层的编程例子。写在最前面的日志处理边编程边执行,查看日志输出,是最直接的反馈,以感受学习的进度。对于 ffmpeg 的日志,需要提前这样处理:/* log.c /#include <stdio.h>#include <stdlib.h>#include <string.h>#include <libavutil/log.h>// 定义输出日志的函数,留白给使用者实现extern void Ffmpeglog(int , char);static void log_callback(void avcl, int level, const char fmt, va_list vl) { (void) avcl; char log[1024] = {0}; int n = vsnprintf(log, 1024, fmt, vl); if (n > 0 && log[n - 1] == ‘\n’) log[n - 1] = 0; if (strlen(log) == 0) return; Ffmpeglog(level, log);}void set_log_callback(){ // 给 av 解码器注册日志回调函数 av_log_set_callback(log_callback);}/ main.c /#include <stdio.h>#include <stdlib.h>#include <string.h>void Ffmpeglog(int l, char t) { if(l <= AV_LOG_INFO) fprintf(stdout, “%s\n”, t);}ffmpeg 有不同等级的日志,本文只需使用 AV_LOG_INFO 即可。第一步,查看音视频格式信息料理食材的第一步,得先懂得食材的来源和特性。来源,互联网在线观看(http/rtmp)、播放设备上存储的视频文件(file)。格式,如何查看视频文件的格式呢,以下有 unix 命令行示例,至于 windows 系统,查看文件属性即可。# linux 上查看视频文件信息ffmpeg -i example.mp4以某个mp4文件为例,输出:Input #0, mov,mp4,m4a,3gp,3g2,mj2, from ’example.mp4’: Metadata: major_brand : isom minor_version : 512 compatible_brands: isomiso2avc1mp41 encoder : Wxmm_900012345 Duration: 00:00:58.21, start: 0.000000, bitrate: 541 kb/s Stream #0:0(und): Video: h264 (High) (avc1 / 0x31637661), yuv420p, 368x640, 487 kb/s, 24 fps, 24 tbr, 12288 tbn, 48 tbc (default) Metadata: handler_name : VideoHandler Stream #0:1(und): Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 48 kb/s (default) Metadata: handler_name : SoundHandler根据命令输出信息,视频文件中有两个 stream, 即 video 与 audio,视频流与音频流。stream 0, 是视频数据,编码格式为h264,24 fps 意为 24 frame per second,即每秒24帧,比特率487 kb/s,stream 1, 是音频数据,编码格式为acc,采样率44100 Hz,比特率48 kb/s【编程实操】读取音视频流的格式信息在互联网场景中,在线观看视频才是常见需求,那么,计算机如何读取视频流的信息呢,下面以 ffmpeg 代码讲述 / C代码例子,省略了处理错误的逻辑 */ AVFormatContext *fmt_ctx = NULL; // AV 格式上下文 AVIOContext *avio_ctx = NULL; // AV IO 上下文 unsigned char *avio_ctx_buffer = NULL; // input buffer fmt_ctx = avformat_alloc_context();// 获得 AV format 句柄 avio_ctx_buffer = (unsigned char )av_malloc(data_size); // ffmpeg分配内存的方法,给输入分配缓存 / fread(file) or memcpy(avio_ctx_buffer, inBuf, n) 省略拷贝文件流的步骤 */ // 给 av format context 分配 io 操作的句柄,必要传参:输入数据的指针、数据大小、write_flag=0表明buffer不可写,其他参数忽略,置 NULL avio_ctx = avio_alloc_context(avio_ctx_buffer, data_size, 0, NULL, NULL, NULL, NULL); fmt_ctx->pb = avio_ctx; // 打开输入的数据流,读取 header 的格式内容,注意必须的后续处理 avformat_close_input() avformat_open_input(&fmt_ctx, NULL, NULL, NULL) // 获取音视频流的信息 avformat_find_stream_info(fmt_ctx, NULL) ffmpeg 有一个方法直接打印音视频信息av_dump_format(fmt_ctx, 0, NULL, 0);print: (源码的输出格式凌乱,笔者整理过)Input #0, mov,mp4,m4a,3gp,3g2,mj2, from ‘(null)’:Stream #0:0 : Video: h264 (High) (avc1 / 0x31637661), yuv420p, 368x640, 487 kb/s 24 fps,Stream #0:1 : Audio: aac (LC) (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 48 kb/s实践编程,获取音视频信息,当然需要细致地调用API,看下面代码查看音视频流的索引、类型、解码器类型 avformat_find_stream_info(fmt_ctx, NULL); for(int i=0; i<fmt_ctx->nb_streams; i++){ AVStream *stream = fmt_ctx->streams[i]; AVCodecParameters *codec_par = stream->codecpar; av_log(NULL, AV_LOG_INFO, “find audio stream index=%d, type=%s, codec id=%d”, i, av_get_media_type_string(codec_par->codec_type), codec_par->codec_id); }print:find audio stream index=0, type=video, codec id=27find audio stream index=1, type=audio, codec id=86018看到没,上面代码只获得解码器的id值(枚举类型),那么解码器的信息呢,加上下面代码,可以看到音视频流的格式,同时获得解码器,方便“解码步骤”使用。 AVCodec decodec = NULL; decodec = avcodec_find_decoder(codec_par->codec_id); // 获得解码器 av_log(NULL, AV_LOG_INFO, “find codec name=%s\t%s”, decodec->name, decodec->long_name);print:find audio stream index=0, type=video, codec id=27find codec name=h264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10find audio stream index=1, type=audio, codec id=86018find codec name=aac AAC (Advanced Audio Coding)关于视频,可以查看帧率(一秒有多少帧画面) // 获得一个分数 AVRational framerate = av_guess_frame_rate(fmt_ctx, stream, NULL); av_log(NULL, AV_LOG_INFO, “video framerate=%d/%d”, framerate.num, framerate.den);print:video framerate=24/1至此,我们掌握了如何利用 ffmpeg 的 C 语言 API 来读取音视频文件流的信息第二步,解码简单说一下音视频文件的解码过程,对大部分音视频格式来说,在原始流的数据中,不同类型的流会按时序先后交错在一起,这是多路复用,这样的数据分布,即有利于播放器打开本地文件,读取某一时段的音视频时,方便进行fseek操作(移动文件描述符的读写指针);也有利于网络在线观看视频,“空投”从某一刻开始播放视频,从文件某一段下载数据。直观的看下面的循环读取文件流的代码 / begin 解码过程 */ AVPacket *pkt; AVFrame frame; // 分配原始文件流packet的缓存 pkt = av_packet_alloc(); // 分配 AV 帧 的内存 frame = av_frame_alloc(); // 在循环中不断读取下一个文件流的 packet 包 while (av_read_frame(fmt_ctx, pkt) >= 0) { if(pkt->size){ / demux 解复用 原始流的数据中,不同格式的流会交错在一起(多路复用) 从原始流中读取的每一个 packet 的流可能是不一样的,需要判断 packet 的流索引,按类型处理 / if(pkt->stream_index == video_stream_idx){ // 此处省略处理视频的逻辑 }else if(pkt->stream_index == audio_stream_idx){ // 此处省略处理音频的逻辑 } } av_packet_unref(pkt); av_frame_unref(frame); } / end 解码过程 */ // flush data avcodec_send_packet(video_decodec_ctx, NULL); avcodec_send_packet(audio_decodec_ctx, NULL);上面代码是对音视频流进行解复用的主要过程,在循环中分别处理不同类型的流数据,到了这一步,就是使用解码器对循环中获取的 packet 包进行解码。解码前的准备ffmepg 中,解码工具需要初始化好两个指针,一个是解码器,一个是解码器上下文,上下文是用来存储此次操作的变量集合,比如 io 的句柄、解码的帧数累加值,视频的帧率等等。让我们重新编写上面读取音视频流的循环,给音视频流分别分配好这两个指针,并且处理好错误返回值。(下面代码的 goto 语句暂且略过,后面再提) // find codec int video_stream_idx = -1, audio_stream_idx = -1; AVStream *video_stream = NULL, *audio_stream = NULL; AVCodecContext *video_decodec_ctx=NULL, *audio_decodec_ctx=NULL; // AVFormatContext.nb_stream 记录了该 URL 中包含有几路流 for(int i=0; i<fmt_ctx->nb_streams; i++){ AVStream *stream = fmt_ctx->streams[i]; AVCodecParameters *codec_par = stream->codecpar; AVCodec *decodec = NULL; AVCodecContext *decodec_ctx = NULL; av_log(NULL, AV_LOG_INFO, “find audio stream index=%d, type=%s, codec id=%d”, i, av_get_media_type_string(codec_par->codec_type), codec_par->codec_id); // 获得解码器 decodec = avcodec_find_decoder(codec_par->codec_id); if(!decodec){ av_log(NULL, AV_LOG_ERROR, “fail to find decodec\n”); goto clean2; } av_log(NULL, AV_LOG_INFO, “find codec name=%s\t%s”, decodec->name, decodec->long_name); // 分配解码器上下文句柄 decodec_ctx = avcodec_alloc_context3(decodec); if(!decodec_ctx){ av_log(NULL, AV_LOG_ERROR, “fail to allocate codec context\n”); goto clean2; } // 复制流信息到解码器上下文 if(avcodec_parameters_to_context(decodec_ctx, codec_par) < 0){ av_log(NULL, AV_LOG_ERROR, “fail to copy codec parameters to decoder context\n”); avcodec_free_context(&decodec_ctx); goto clean2; } // 初始化解码器 if ((ret = avcodec_open2(decodec_ctx, decodec, NULL)) < 0) { av_log(NULL, AV_LOG_ERROR, “Failed to open %s codec\n”, decodec->name); return ret; } if( stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ // 视频的属性,帧率,这里 av_guess_frame_rate() 非必须,看业务是否需要使用帧率参数 decodec_ctx->framerate = av_guess_frame_rate(fmt_ctx, stream, NULL); av_log(NULL, AV_LOG_INFO, “video framerate=%d/%d”, decodec_ctx->framerate.num, decodec_ctx->framerate.den); video_stream_idx = i; video_stream = stream; video_decodec_ctx = decodec_ctx; } else if( stream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO){ audio_stream_idx = i; audio_stream = stream; audio_decodec_ctx = decodec_ctx; } }以上方式是循环读取文件的所有stream,便于查看文件中有什么流,如视频、音频、字幕等,若是业务需求,只要对单独一个流(比如视频),可以用以下方式获取特定的流。 if (avformat_find_stream_info(fmt_ctx, NULL) < 0) { av_log(NULL, AV_LOG_ERROR, “Could not find stream information\n”); goto clean1; } ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (ret < 0) { av_log(NULL, AV_LOG_ERROR, “Could not find %s stream\n”, av_get_media_type_string(type)); return ret; } int stream_index = ret; AVStream st = fmt_ctx->streams[stream_index];解码的循环修改上面解码的循环,以视频流为例,如何从流中读取帧,为便于理解,在关键地方有清楚的注释。 while (av_read_frame(fmt_ctx, pkt) >= 0) { if(pkt->size){ / demux 解复用 原始流的数据中,不同格式的流会交错在一起(多路复用) 从原始流中读取的每一个 packet 的流可能是不一样的,需要判断 packet 的流索引,按类型处理 / if(pkt->stream_index == video_stream_idx){ // 向解码器发送原始压缩数据 packet if((ret = avcodec_send_packet(video_decodec_ctx, pkt)) < 0){ av_log(NULL, AV_LOG_ERROR, “Error sending a packet for decoding, ret=%d”, ret); break; } / 解码输出视频帧 avcodec_receive_frame()返回 EAGAIN 表示需要更多帧来参与编码 像 MPEG等格式, P帧(预测帧)需要依赖I帧(关键帧)或者前面的P帧,使用比较或者差分方式编码 读取frame需要循环,因为读取多个packet后,可能获得多个frame / while(ret >= 0){ ret = avcodec_receive_frame(video_decodec_ctx, frame); if(ret == AVERROR(EAGAIN) || ret == AVERROR_EOF){ break; } / DEBUG 打印出视频的时间 pts = display timestamp 视频流有基准时间 time_base ,即每 1 pts 的时间间隔(单位秒) 使用 pts * av_q2d(time_base) 可知当前帧的显示时间 / if(video_decodec_ctx->frame_number%100 == 0){ av_log(NULL, AV_LOG_INFO, “read video No.%d frame, pts=%d, timestamp=%f seconds”, video_decodec_ctx->frame_number, frame->pts, frame->pts * av_q2d(video_stream->time_base)); } / 在第一个视频帧读取成功时,可以进行: 1、若要转码,初始化相应的编码器 2、若要加过滤器,比如水印、旋转等,这里初始化 filter */ if (video_decodec_ctx->frame_number == 1) { }else{ } av_frame_unref(frame); } }else if(pkt->stream_index == audio_stream_idx){ } } av_packet_unref(pkt); av_frame_unref(frame); }回收内存上文的代码中,多次出现 goto 语句,我认为适当的使用 goto 使编程更加方便,比如执行过程结束的清理工作,以下是回收 ffmpeg AV 库产生的各种变量的内存,C/C++语言编程都需要多注意这一点。clean5: av_frame_free(&frame); //av_parser_close(parser);clean4: av_packet_free(&pkt);clean3: if(NULL != video_decodec_ctx) avcodec_free_context(&video_decodec_ctx); if(NULL != audio_decodec_ctx) avcodec_free_context(&audio_decodec_ctx);clean2: av_freep(&fmt_ctx->pb->buffer); av_freep(&fmt_ctx->pb);clean1: avformat_close_input(&fmt_ctx);end: return ret;自由发挥看到了这里,可以说入门 ffmpeg 编程了,什么,你问后面的转码怎么做?笔者就留白了,本文已经介绍了最基本的解码过程了,编码也就是逆向过程,我建议阅读 ffmepg 官方源码的example,以及多了解音视频各种格式的知识。实际例子我提供两个小例子在 github 上转码GIF生成缩略图请安装好 linux 下 ffmepg 环境,找到例子代码里的 Makefile 文件编译,例如:make -f Makefile_test_dump_info以后我会将这两个小例子修改,实现跨语言调用,如 nodejs addon 或 golang cgoReferenceffmpeg example (本文代码就是从example改过来的) ...

January 22, 2019 · 5 min · jiezi