• [问题求助] 华为有没有AI代码生成服务?
    想做一个系统生成器,需要一款相对成熟的AI代码生成器,求推荐
  • [技术干货] C语言-语句(if,for,while,switch,goto,return,break,continue)
    ## 一、前言 这篇文章作为C语言基础知识点,介绍C语言常用的几个语句的用法、规则、使用案例。 **介绍的语句如下:** ```cpp if..else 判断语句 for循环语句 while循环语句 do..while循环语句 switch 语句 goto 语句 return 语句 break 语句 continue 语句 ``` 第二章介绍语法使用规则、使用案例,第三章列出了一些练习题,用于结合第二章介绍的语句完成知识点巩固。 ## 二、知识点与案例代码 ### 2.1 if语句语法规则、使用案例 **if语句语法:** ```cpp //形式1 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } //形式2 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } else //条件为假的时候执行 (else的语句块可以选择的,可以写也可以不写) { } //形式3 if(条件表达式>) //条件为真的时候执行 { ...执行的代码.. } else if(条件表达式>) //条件为假的时候执行 (else的语句块可以选择的,可以写也可以不写) { ...执行的代码.. } else if(条件表达式>) { ...执行的代码.. } ........ 条件表达式> 里可以写什么代码? 写的语句执行完必须返回具体的结果。 比如:12>34 比如: if(printf("12345")) ``` **使用案例** ```cpp #include int main(int argc,char *argv[]) { int a=100; int b=20; int c=30; if(a>b) { printf("a>b\n"); } if(a>b)printf("a>b\n"); //容易出错的地方 if(a==0) //if(a=0) { printf("a>b a>c\n"); } //容易出错的地方 if(a&&b)//if(a&0) { printf("a>b a>c\n"); } return 0; } /* 逗号、分号 逗号:间隔符号。 比如: int a,b,c,d; ! 逻辑非 && 逻辑与 -- 并且 || 逻辑或 --或者 & 按位与(3&1)、取地址(&a) * 乘号(a*b) 、指针(*a)、 *(块注释) */ #include int main(int argc,char *argv[]) { int year; printf("输入年份:"); scanf("%d",&year); //判断平年和闰年 条件:能被4整除并且不能被100整除 条件2:能被400整除 if((year%4==0 && year%100!=0) || (year%400==0)) { printf("闰年\n"); } else { printf("平年\n"); } //判断平年和闰年 条件:能被4整除并且不能被100整除 条件2:能被400整除 if(year%4==0 && year%100!=0) { printf("闰年\n"); } else if(year%400==0) { printf("闰年\n"); } else { printf("平年\n"); } return 0; } ``` ### 2.2 while、do..while语句 ```cpp //循环语句 while(条件表达式>) { ...执行的代码.. } do { ...执行的代码.. }while(条件表达式>); #include int main(int argc,char *argv[]) { int a=5; int b=5; //循环语句 a-- 、a=a-1、a-=1; while(a--) { printf("a=%d\n",a);//4\3\2\1\0 } //循环语句 do { printf("a=%d\n",b);//\5\4\3\2\1\0 }while(b--); return 0; } ``` ### 2.3 for语句 ```cpp #include int main(int argc,char *argv[]) { int a=0; //写法1 for(初始化语句>;条件表达式>;自增/自减>) { } 比如: for(a=0;a10;a++) { } //写法2 for(;条件表达式>;自增/自减>) { } 比如: for(;a10;a++) { } //写法3 for(;;) //条件永远为真 { } 等价于 while(1)//条件永远为真 { } return 0; } #include int main(int argc,char *argv[]) { //打印99乘法口诀表 int i,j; for(i=1;i=9;i++) { for(j=1;j=i;j++) { printf("%dx%d=%2d ",i,j,i*j); } printf("\n"); } return 0; } /* 1x1=1 2x1=1 2x2=4.... 3x1=1 3x2=6 3x3=9 ..... ................ */ ``` ### 2.4 break语句 作用: 跳出当前的一层循环,或者跳出switch语句。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { break; //跳出当前一层循环 } printf("cnt=%d\n",cnt++); //25 } } return 0; } ``` ### 2.5 goto跳转语句 可以在函数内设置标签,任意跳转。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { goto AA; //跳转语句 } printf("cnt=%d\n",cnt++); //5 } } AA: printf("程序执行完毕.\n"); return 0; } ``` ### 2.6 continue语句 跳出本次循环,继续执行下一次新的循环。 ```cpp #include int main(int argc,char *argv[]) { int i,j,cnt=1; for(i=0;i5;i++) { for(j=0;j10;j++) { if(j==5) { continue; //跳出当前一次循环 } printf("cnt=%d\n",cnt++); // } } return 0; } ``` ### 2.7 switch 分支语句(C89标准和C99标准) ```cpp #include int main(int argc,char *argv[]) { int a; scanf("%d",&a); switch(a) { case 1: printf("选择1.\n"); break; case 2: printf("选择2.\n"); break; case 3: printf("选择3.\n"); break; case 4: case 5: case 6: printf("选择4,5,6\n"); break; default: printf("选择默认值.\n"); break; } return 0; } ``` ## 三、练习题 ### 3.1 输入整数,然后使用二进制方式输出(数据传输) ```cpp 0x23; ---8次 00100011 #include int main() { unsigned int a,i; int flag=0; printf("输入整数:"); scanf("%d",&a); for(i=0;i32;i++) { //if(a&0x1) //从低位开始判断 //if(a&0x80000000) //从高位开始判断 if(a&(131)) //从高位开始判断 { flag=1; printf("%d",1); } else { if(flag) { printf("%d",0); } } a=1; //把a的左边移动 (高位溢出、低位补0) } printf("\n"); return 0; } ``` ### 3.2 打印所有水仙花数。 所谓水仙花是指一个三位数,其各位数字的立方和等于该数 ```cpp #include int main() { int i; int a, b, c; for (i = 100; i = 999; i++) { a = i / 100; b = i % 100 / 10; c = i % 10 / 1; if ((a*a*a + b*b*b + c*c*c) == i) { printf("%d ", i); } } return 0; } ``` ### 3.3 百元买百鸡 公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只一元,问一百元买一百只鸡有几种买法。 ```cpp #include int main() { int m, g, x; int m_max; int g_max; int x_max; int q, cnt; //cnt=数量 q:钱 printf("公鸡每只 5 元,母鸡每只 3 元,小鸡 3 只一元\n"); printf("请输入要买鸡的数量:\n"); scanf("%d", &cnt);// 100 100 printf("请输入要买鸡的钱:\n"); scanf("%d", &q);// 100 100 /*1. 判断输入的数据是否合理*/ if (q>0 && cnt>0) { m_max = cnt / 3; // 母鸡 100 /3 =33 g_max = cnt / 5; // 公鸡 100 /5 =20 x_max = (cnt / 1) * 3; // 小鸡 100 /1*3 =300 for (g = 0; g/ 循环判断可能 { for (m = 0; m/总数量-公鸡数量-母鸡数量 =小鸡的数量 if (x + g + m == cnt && x / 3 + g * 5 + m * 3 == q) //买鸡的总数等于总鸡数和总钱数等于买鸡的钱数就输出 { printf("公鸡=%d\t", g); printf("母鸡=%d\t", m); printf("小鸡=%d\t\n", x); } } } } else printf("输入有误\n"); return 0; } ``` ### 3.4 打印正三角形 ```cpp #include /* 1 121 12321 1234321 */ int main() { int i, j; int len; scanf("%d", &len); for (i = 1; i = len; i++) //总行数 { /*1. 打印空格*/ for (j = 1; j = len - i; j++) { printf(" "); } /*2. 打印前面一半*/ for (j = 1; j = i; j++) { printf("%d", j); } /*3. 打印后面一半*/ for (j = i - 1; j >= 1; j--) { printf("%d", j); } printf("\n"); } return 0; } ``` ### 3.5 打印倒三角形 ```cpp #include int main() { int i, j; int len; scanf("%d", &len); for (i = len; i >= 1; i--) //循环的总次数 { /*1. 打印空格*/ for (j = len; j>i; j--) { printf(" "); } /*2. 打印前面部分*/ for (j = 1; j = i; j++) { printf("%d", j); } /*3. 打印后半部分*/ for (j = i - 1; j >= 1; j--) { printf("%d", j); } printf("\n"); } return 0; } ```
  • [加速器] 【案例分享】编译优化带来的性能提升以及可能造成的问题
     编译优化编译器已经发展优化很多年了,一些关于指令重排,精简操作指令,尽量去满足cpu的流水操作,对程序分支进行预测,重新调整代码执行顺序,对简单的程序,使用循环展开等等这些优化代码的操作,大部分的编译器就可以完成。因此本文并不会着重介绍这些性能优化方法,尤其是指令重排和精简指令操作这两种。这些优化,可以通过一些优化选项来让编译器帮我们完成。1.1      Gcc的四级优化-O0:不做任何优化,这是默认的编译选项。 -O1:对程序做部分编译优化,对于大函数,优化编译占用稍微多的时间和相当大的内存。使用本项优化,编译器会尝试减小生成代码的尺寸,以及缩短执行时间,但并不执行需要占用大量编译时间的优化。 它主要对代码的分支,常量以及表达式等进行优化。 -O2:会尝试更多的寄存器级的优化以及指令级的优化,它会在编译期间占用更多的内存和编译时间。Gcc将执行几乎所有不包含时间和空间折中的优化。当设置O2选项时,编译器并不进行循环展开以及函数内联。与O1比较而言,O2优化增加了编译时间的基础上,提高了生成代码的执行效率。 -O3:在O2的基础上进行更多的优化,例如使用伪寄存器网络,普通函数的内联,以及针对循环的更多优化。-Os:主要是对程序的尺寸进行优化。打开了大部分O2优化中不会增加程序大小的优化选项,并对程序代码的大小做更深层的优化。(通常我们不需要这种优化)1.2      编译优化可能带来的问题调试问题:任何级别的优化都将带来代码结构的改变。例如:对分支的合并和消除,对公用子表达式的消除,对循环内load/store操作的替换和更改等,都将会使目标代码的执行顺序变得面目全非,导致调试信息严重不足。 内存操作顺序改变所带来的问题:在O2优化后,编译器会对影响内存操作的执行顺序。例如:-fschedule-insns允许数据处理时先完成其他的指令;-fforce-mem有可能导致内存与寄存器之间的数据产生类似脏数据的不一致等。对于某些依赖内存操作顺序而进行的逻辑,需要做严格的处理后才能进行优化。例如,采用volatile关键字限制变量的操作方式,或者利用barrier迫使cpu严格按照指令序执行的。精简指令操作带来的精度问题:编译优化,会精简指令操作,将某两条指令合并成一条指令等。在数学计算中,先后计算会带来精度不一致的问题。比如计算公式A*B-C*D,我们希望先计算C*D,再计算A*B,最后计算减法,于是代码中用两个乘法和一个减法指令实现此功能,然而编译器会将此三条指令优化成两个指令,即一个乘法指令和一个乘减指令。从而会先计算A*B =X,再计算X-C*D,此类问题在双精度的浮点计算时,会有较大的精度差异。1.3      常用解决方法对于上述编译优化可能引入的一些问题,一般常用的解决方法是用内联汇编的方式重新封装需要的指令。一般在一个工程中,不同代码段,被优化引入的问题是不同的,我们可能无法去修改编译选项。比如上述例子中,因为精简指令带来的精度问题,如果在编译选项中关闭精简指令,势必会影响整个工程的性能。如果在相关函数中告诉编译器禁止优化,那么其他优化方法,此函数也享受不到了。但是如果使用内联汇编的方式,此部分代码编译后,会按照内联的汇编中的指令以及指令顺序执行,这样我们就可以控制问题代码的逻辑同时其他部分继续享受编译器的优化。下面用一个简单例子来说明://下列代码为了计算A*B+E*F以及G*H-C*D        float64x2_t X = vmulq_f64(A, B);//乘法指令,计算X=A*B        float64x2_t Y = vmulq_f64(C, D);//乘法指令,计算Y=C*D        Y = vnegq_f64(Y);//取反,即Y=-Y        float64x2_t RST1 = vmlaq_f64(X, E, F);//乘加指令,计算X+E*F        float64x2_t RST2= vmlaq_f64(Y, G, H);//乘加指令,计算Y+G*H代码中的实现顺序为先计算A*B=X,再计算X+E*F,先计算Y=C*D,再计算-Y+G*H。假设这是我们希望的逻辑计算顺序。开了编译优化以后,上面指令会被精简优化,取反指令会和乘加指令合并成乘减指令。优化后的公式就变成了A*B+E*F以及G*H-C*D,先计算A*B和G*H,再分别用一条乘加指令和乘减指令完成计算。此与我们希望的运算顺序不同,上述优化是因为优化了乘加指令导致的,因此,我们只用重新封装乘加指令就可以了。当程序到乘加指令的时候,走的是内联汇编版本,按照写的代码执行。如封装的内联汇编如下:FORCE_INLINE float64x2_t vmlaq_f64_ext(float64x2_t a, float64x2_t b, float64x2_t c){    float64x2_t result;    __asm__ __volatile__ (        "fmla %0.2d, %2.2d, %3.2d"        : "=w"(result)        : "0"(a), "w"(b), "w"(c)        : /* No clobbers */    );    return result;}
  • [GCC for op...] GCC for openEuler -mcmodel选项详解
    # GCC for openEuler -mcmodel选项详解 ## 导语 GCC for openEuler是基于开源GCC开发的编译器工具链(包含编译器,汇编器,链接器),在openEuler社区开源发布,并通过鲲鹏社区免费提供二进制包,支持包含ARM、x86在内的多种处理器架构。 本文将向大家详细介绍-mcmodel选项的作用以及GCC for openEuler 在-mcmodel选项上做的新功能支持[1]。 ## 背景 编译的过程中,编译器是不知道要操作的数据在哪里的,计算数据地址的工作是在链接阶段实现的。也就是说,编译器需要先把拿取数据的汇编指令定下来,在编译结束之后,链接器进行重定位计算时再填上指令的操作数是多少。那就有一个问题,编译器如何选择一个合适的指令拿取数据? **PC相对寻址** 在考虑这个问题前,我们先了解一下PC相对地址。PC(Program Count)特指PC寄存器(以下都用PC表示),是计算机处理器内部的一个专用寄存器,用来表示下一个将被执行的指令的地址。在我们的程序中,如果某一条指令访问的符号(变量,函数等)是基于当前指令的相对地址,我们就称这是**PC相对寻址**。 例如跳转指令`b, <label>`中的 `<lable>` 就是**偏移量**,并不是绝对地址。在链接阶段,链接器会根据符号出现的位置计算出正确的偏移,编码到指令中去。程序执行时到这条指令时,就会把`<lable>`所代表的偏移量与当前PC值相加,得到要跳转的指令地址。 类似的还有ADR,ADRP指令。ADR/ADRP分别是获取某个符号的地址/页面地址,并把它们存放到指定的寄存器中。它们的格式是这样的:`ADR Rd, <label>`, `ADRP Rd, <label>`, 这里的`<label>`都是代表当前指令位置的偏移量。 `<label>`受到指令位域的限制,所以偏移量都是有限制的。比如说`b, <label>` 可以基于当前PC有±128MB的寻址范围,`ADR Rd, <label>`有±1MB的寻址范围,`ADRP Rd, <label>`有±4GB的寻址范围。 链接器在链接时会把各个目标文件进行合理布局,使得跳转、函数调用、变量访问等操作都不会离当前指令特别远。通常情况下,使用前面提到的指令能满足绝大多数的情景,但是如果指令要访问的符号超出了4GB(32位)的范围,这时用默认的符号取值方式就会出错。 ## 案例 比如说在AArch64后端如果待链接符号的距离超过4GB(如图1),编译的时候又决定使用ADRP指令(32bit寻址范围,如图2)。此时如果使用这种方式去链接,则会报relocation truncated to fit这样的错(如图3)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115223rn6mznkcmbscp5ww.png) 图1:符号相对于.bss段的偏移大于4GB ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115230mhnipbboql5nnae5.png) 图2:指令集中对于ADRP指令的描述,可见其寻址范围只在±4GB ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115236bb3qbrxxx1aw9i5i.png) 图3:CESM代码中出问题的字段描述,`R_AARCH64_ ADR_PREL_PG_HI21`对应ADRP指令进行相对PC寻址 那么怎么解决这个问题?在这种情况下需要指导编译器:符号可能特别远(超过4GB),需要生成恰当的指令来获取符号地址。那么编译器就不会再用传统的ADRP这样的指令,而是采用其他的办法。 对此,GCC提供了`-mcmodel`参数,用于指导编译器应该使用哪种模型来生成指令。 下面是GCC的`-mcmodel`对AArch64架构的的官方说明: > -mcmodel=tiny\ > Generate code for the tiny code model. The program and its statically defined symbols must be within 1MB of each other. Programs can be statically or dynamically linked.\ > -mcmodel=small\ > Generate code for the small code model. The program and its statically defined symbols must be within 4GB of each other. Programs can be statically or dynamically linked. This is the default code model.\ > -mcmodel=large\ > Generate code for the large code model. This makes no assumptions about addresses and sizes of sections. Programs can be statically linked only. The -mcmodel=large option is incompatible with -mabi=ilp32, -fpic and -fPIC. `-mcmodel`选项指导编译器做出这样一种假设:代码里所有符号的位置都在某个位宽范围之内。比如`-mcmodel=small`,就是假设所有符号都在4GB范围内,32bit的位宽就可以找到符号的位置,那我们使用ADRP指令就可以了。 但是假设不成立的时候,比如上面的情况,ADRP指令不再适用,需要用位宽更大的指令。这个时候就需要增大`-mcmodel`的预设,使用`-mcmodel=large`,变更寻址方式为LDR指令。LDR指令只能绝对寻址,但是有更大的寻址范围。如果你的应用可以非地址无关编译的话,那么`-mcmodel=large`理论上可以解决所有的问题。 而例如HPC场景中的CESM应用在符号超过4GB寻址范围的时候,作为一些共享库,仍然需要按照地址无关代码(position-independent code,PIC) 的方式编译(-fPIC / -fpic地址无关功能),可以说地址无关代码是动态共享库必须的。这时LDR指令也不再适用,因为GCC对于AArch64的支持非常有限,它仅支持非地址无关代码。这是AArch64独有的一类问题。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115247hj0okyvi92ys76yk.png) 图4:aarch64 `-mcmodle=large`时的寻址方式 在x86上有可以大位宽操作的`mov`指令,它可以实现4GB以上的地址无关寻址。根据与x86后端对比可以发现,x86后端在`-mcmodel=medium`的时候之所以还可以生成地址无关代码,主要原因是其寻址方式还是相对PC寻址。与`-mcmodel=small`相比唯一的变动是相对寻址的指令由`mov`变成了`movabs`,可以进行更大范围的寻址(64bit)。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/11534776zvvrndb3pxqwsa.png) 图5:x86 ABI 中对于`-mcmodle=medium`、`-mcmodle=large`的描述 反观AArch64后端,其实相对PC寻址的指令和64bit的加法指令都是有的,甚至是64位的相对PC寻址方式在ABI中都是有的(如图6),缺少的是这种重定位方式。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1153556gwxcjxe5z9bogjo.png) 图6: AArch64 ABI 中关于64位相对PC寻址的描述 ## -mcmodel=medium, -mlarge-data-threshold=n GCC for openEuler根据上述问题的痛点,新开发了`-mcmodel=medium`, `-mlarge-data-threshold=n`两个选项。此选项使能了32bit之外的动态取址操作。在使用`-mcmodel=medium`时,对于符号size大于`aarch64_data_threshold`的符号使用**通过mov序列来获取PC值的offset,再与PC值相加**的方式实现64bit的相对PC寻址,在地址无关选项打开时,可以实现64bit相对PC寻址,获取GOT表入口,并且通过mov序列+LDR方式获取符号。 说明:`aarch64_data_threshold`的默认值为2^16 = 65536,用户可以使用`-mlarge-data-threshold=n`选项指定大符号的阈值为n。 ### 举例 如图7所示,假设`foovar`的符号距离寻址指令的距离大于4GB,`-mcmodel=small`会使用ADRP+ADD指令进行符号拿取,而`foovar`在链接时计算距离的方式是如**使用方法**中的.bss+size方式,在链接时会报`relocation truncated to fit`错误。在此使用图8中的mov序列+PC寻址方式可将寻址范围扩大至64位,解决由于地址溢出导致的报错。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115415ma3rw5yimprdy7xc.png) 图7:smallcode model寻址方式 在这种模式下,通过`adrp`和`add`指令获取`foovar`的地址。`adrp`是PC相对寻址,它会把`foovar`的页地址偏移量与当前PC值相加,并存储到`x0`寄存器,下一条指令`add`把页内地址(低12位)再加到`x0`寄存器上,这样就得到了`foovar`的地址。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1154194wi8hxhxv25zq8qi.png) 图8:mov序列+PC寻址方式 可以看出这里使用了`movz`, `movk`, `adr`, `sub`, `add`这样一系列的指令最终得到了`foovar`的地址。`movz`和3条`movk`指令的作用是把`foovar`的64位的偏移量分4次,每次转存16位,依次存放到了x0寄存器,`adr x8, .`的作用是获取当前的PC值,`sub`是对PC值做一些修正,然后`add`是把64位偏移量与修正后的PC值相加。这样就得到具有64位PC相对地址的`foovar`地址了。 ### 使用方法 用例: libdemo.cpp ```cpp libdemo.cpp #include <iostream> char arr[10][1*1024*1024*1024]; void set_and_print(){ arr[8][0]='A'; std::cout << arr[8][0] << std::endl; } ``` 上述代码定义了一个二维数组`arr`,第一维有10个元素,每一个元素又是一个总大小为1GB的字符数组。在访问`arr[8][0]`时需要偏移8GB,已经超过了`ADR`, `ADRP`这样的取值范围。 main.cpp ```cpp extern char arr[10][1*1024*1024*1024]; void set_and_print(); int main(){ set_and_print(); return 0; } ``` 主程序会使用共享库中的`set_and_print`函数。 现在我们来编译上面的代码。如下图所示,如果不指定`-mcmodel`参数就会报`relocation truncated to fit`错误;当程序指定`-mcmodel=large`时,又与-fpic冲突,无法生成动态共享库。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115429zarqzuytqgldk7nu.png) 图9:不指定`-mcmodel`,或者指定`-mcmodel=large`进行编译,编译失败 当我们使用GCC for openEuler开发的`-mcmodel=medium`和`-mlarge-data-threshold=1`后,动态共享库被成功创建了,主程序也能正常调用它,并且得到正确的结果。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115437xqpvju6swe765vqm.png) 图10:指定-mcmodel=medium -mlarge-data-threshold=1 进行编译,编译成功 我们对共享库进行反编译查看汇编代码就会看到,取址指令已经是movz, movk这样的序列了。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115459f5g4w3kuytygifs3.png) 编译命令: ```bash c++ libdemo.cpp -fpic -shared -o libdemo.so -mcmodel=medium -mlarge-data-threshold=1 c++ main.cpp libdemo.so -o main ``` 运行主程序: ```bash ./main ``` ## 总结 该选项通过软件模拟的方式,使用多条指令去模拟`movabs`指令,使得在HPC领域一些需要大范围地址无关寻址的应用能够平滑地从其他平台迁移到鲲鹏平台中来。 所以在GCC for openEuler使用过程中,若出现`relocation truncated to fit`错误,可以尝试添加编译选项`-mcmodel=medium -mlarge-data-threshold=1`解决。 时间问题暂时写到此处,后续会继续更新一些GCC for openEuler或者毕昇编译器相关优化选项的介绍,感兴趣的朋友敬请博客留言,也可以点击文末**阅读原文**进入GCC for openEuler网页下载使用GCC for openEuler。 ## 参考 [1] https://bbs.huaweicloud.com/blogs/272527 [2] https://www.hikunpeng.com/developer/devkit/compiler/gcc [3] https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html ## 后记 欢迎加入Compiler SIG交流群与大家共同交流学习编译技术相关内容,扫码添加小助手微信邀请你进入Compiler SIG交流群。 ![image.png](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/115534xw07sqvtqm9iloxa.png) ------ 原文转载自[毕昇编译-GCC for openEuler -mcmodel选项详解](https://mp.weixin.qq.com/s/9OXRMg6xCTSdlhx_44BCvA) ![毕昇编译-二维码.jpg](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202112/25/1156583kgzvnhtfxuzozuy.jpg) 访问GCC for openEuler网页:https://www.hikunpeng.com/developer/devkit/compiler/gcc
  • [技术干货] 编程语言
    计算机编程语言主要包括汇编语言、机器语言以及高级语言,具体内容如下:汇编语言该语言主要是以缩写英文作为标符进行编写的,运用汇编语言进行编写的一般都是较为简练的小程序,其在执行方面较为便利,但汇编语言在程序方面较为冗长,所以具有较高的出错率。机器语言这种语言主要是利用二进制编码进行指令的发送,能够被计算机快速地识别,其灵活性相对较高,且执行速度较为可观,机器语言与汇编语言之间的相似性较高,但由于具有局限性,所以在使用上存在一定的约束性。高级语言所谓的高级语言,其实是由多种编程语言结合之后的总称,其可以对多条指令进行整合,将其变为单条指令完成输送,其在操作细节指令以及中间过程等方面都得到了适当的简化,所以,整个程序更为简便,具有较强的操作性,而这种编码方式的简化,使得计算机编程对于相关工作人员的专业水平要求不断放宽。
  • [高校教学] 实验平台版本说明
    课程套件会基于师生的建议和反馈,结合昇腾全栈软硬件版本、教学周期每半年刷新1次(每学期开课前)。软硬件版本会前向兼容,若出现不兼容请参考版本变更说明。版本刷新后会通过微信等线上途径知会老师,建议授课前获取官网最新版本。 考虑昇腾全栈软件版本迭代、ModelArts上可用版本,确定实验中各组件的版本。依赖ModelArts的实验跟随ModelArts版本进行刷新,其他实验采用最新版本。 | 组件 | 固件驱动 | CANN | MindSpore | MindX | MindStudio | | ------------------ | -------- | ----------- | --------- | ----- | ---------- | | 2021年H1 | 1.0.9 | 3.2.0 | 1.1 | N/A | 2.0.0 | | 2021年H2-ModelArts | 1.0.10 | 3.3.0/5.0.1 | 1.2 | N/A | 3.0.1 | | 2021年H2-手动安装 | 1.0.10 | 5.0.2 | 1.3 | 2.0.2 | 3.0.2 | ## 社区版和商用版 固件驱动和CANN区分社区版和商用版,社区版版本号以`x.x.x.alpha00x`命名,商用版版本号以`x.x.x`命名。社区版约2周发布一个alpha版本,更新较快,可以提前获取最新的功能特性。商用版约3月发布一次,经过严格测试,较为稳定。同一版本(x.x.x)的社区版和商用版,可相互替换使用。Atlas 200 DK可用Atlas 200 AI加速模块的商用版固件驱动和CANN。 ## 版本文档 - MindX SDK:通过[MindX SDK主页](https://www.hiascend.com/software/mindx-sdk)获取,或直接前往[2.0.2](https://support.huaweicloud.com/mindxsdk202/)。 - [MindSpore](https://mindspore.cn/):教程、文档、API。 - MindStudio:通过[MindStudio主页](https://www.hiascend.com/software/mindstudio)获取,或直接前往[3.0.2](https://support.huaweicloud.com/mindstudio302/index.html) - CANN:[社区版](https://www.hiascend.com/?tag=community-developer)、[商用版](https://www.hiascend.com/?tag=commercial-developer),在右上角选择版本。 - Atlas 200 DK:通过[AI开发者套件主页](https://www.hiascend.com/hardware/developer-kit)获取,或直接前往[1.0.10.alpha](https://support.huaweicloud.com/productdesc-Atlas200DK1010/atlas200_DK_pdes_19_0002.html) 不同版本CANN、Atlas 200 DK文档获取方式见:[昇腾社区>开发者>开发者文档](https://www.hiascend.com/?tag=community-developer),如下: ![文档.PNG](https://bbs-img.huaweicloud.com/data/forums/attachment/forum/202108/10/20200311rg0f80sgszbcjl.png) ## 查看版本信息 - MindX SDK:查看SDK安装目录下的`version.info`文件可确认版本信息,安装目录为用户指定,或安装包同一目录。如`cat /usr/local/Ascend/mindx_sdk/mxManufactue_2.0.1.b021/linux-aarch64/mxManufacture/version.info`。 - MindSpore:MindSpore针对Ascend、GPU、CPU平台的pip包名分别为mindspore_ascend、mindspore_gpu、mindspore。通过如`pip show mindspore_ascend`命令可确认MindSpore版本信息。 - MindStudio:查看MindStudio安装目录下的`product-info.json`文件可确认版本信息,或在MindStudio欢迎界面/Help>About界面查看版本信息。 - CANN:查看CANN安装目录下的`version.info`文件可确认版本信息,安装位置通常在`/usr/local/Ascend/`或当前用户目录下,如`cat /usr/local/Ascend/ascend-toolkit/latest/arm64-linux/ascend_toolkit_install.info`。 - 固件驱动:查看固件驱动安装目录下的`version.info`文件可确认版本信息,安装位置通常在`/usr/local/Ascend/`目录下,如`cat /usr/local/Ascend/version.info`或`cat /usr/local/Ascend/driver/version.info`。 ## 接口/功能变更 ### 当前版本 - [MindSpore](https://gitee.com/mindspore/mindspore/blob/master/RELEASE.md):含个版本接口变更说明,见各版本章节的**API Change**小节。 - [MindX SDK 2.0.2](https://support.huaweicloud.com/mindxsdk202/):见mxManufacture/mxVision用户指南的**使用约束、What's New**章节。 - [MindStudio 3.0.2](https://support.huaweicloud.com/mindstudio302/index.html):见版本说明的**版本配套关系、新增特性、问题修复说明**章节。 - CANN 3.3.0(商用版):即5.0.1版本,[C++ ACL废弃接口/返回码列表](https://support.huaweicloud.com/devg-cannApplicationDev330/atlasapi_07_0005.html),[Python ACL废弃接口/返回码列表](https://support.huaweicloud.com/usermanual-cannApplicationDev330/atlaspyapi_07_0002.html) - CANN 5.0.2.alpha003(社区版):[C++ ACL废弃接口/返回码列表](https://support.huaweicloud.com/aclcppdevg-cann502alpha3infer/atlasapi_07_0005.html),[Python ACL废弃接口/返回码列表](https://support.huaweicloud.com/aclpythondevg-cann502alpha3infer/atlaspyapi_07_0002.html) ### 历史版本 - [MindX SDK 2.0.1](https://support.huaweicloud.com/mindxsdk201/):见版本说明**What's New、问题修复说明**章节。 - [MindStudio 3.0.1](https://support.huaweicloud.com/mindstudio301/index.html):见版本说明**What's New、问题修复说明**章节。 ## 版本号变更 为了更好的管理和呈现昇腾软件平台的演进,对部分软件版本号体系进行了重构。历史变更如下,供了解: | 软件类型 | 变更前 | 变更后 | | ---------- | ------------- | -------------- | | CANN社区版 | 20.1.alpha001 | 3.1.0.alpha001 | | CANN社区版 | 20.2.alpha001 | 3.2.0.alpha001 | | CANN社区版 | 3.3.0alpha001 | 5.0.1.alpha001 | | CANN商用版 | 20.0 | 3.0.0 | | CANN商用版 | 20.1 | 3.1.0 | | CANN商用版 | 20.2 | 3.2.0 | | CANN商用版 | 3.3.0 | 5.0.1 | ## 问题支持 1. 在昇腾论坛对应板块上搜索/发帖,专家值守,有问必答。:https://bbs.huaweicloud.com/forum/forum-726-1.html 2. 通过接口人获取帮助。
  • [鲲鹏编译器] 【毕昇编译器】毕昇编译器,让你的代码快到飞起!
    HPC( High Performance Computing,高性能计算)领域主要是解决计算密集型、海量数据处理等业务的计算需求,如科学研究、气象预报、计算模拟等。如何提高计算能力、极致化应用性能成为当前 HPC 领域各大平台最关键的课题之一,编译器在其中发挥着至关重要的作用。毕昇编译器作为一款基于鲲鹏平台的高性能编译器,在编译算法、加速指令集、 Autotuner 等方面对应用场景进行了深度的优化,为开发者提供高效的性能加持。本期由毕昇编译器工程师卜乐为你介绍鲲鹏的性能优化利器——毕昇编译器如何释放鲲鹏的强劲算力。了解毕昇编译器毕昇编译器是基于 LLVM,针对鲲鹏平台进行了深度优化的高性能编译器。除支持 LLVM 通用功能之外,对以下三个方面进行了增强,使得鲲鹏平台的强劲算力能够最大限度地得到释放。高性能编译算法:编译深度优化,内存优化增强,自动矢量化等,大幅提升指令和数据呑吐量。加速指令集:结合 NEON/SVE 等内嵌指令技术,深度优化指令编译和运行时库,发挥鲲鹏架构极致算力。AI 迭代调优:内置 AI 自学习模型,自动优化编译配置,迭代提升程序性能,完成最优编译。毕昇编译器特性架构图当前毕昇编译器已广泛应用于多种 HPC 典型场景,如气象、安防、流体力学等,性能优势已初步体现。其中,SPEC CPU 2017 benchmark 跑分平均优于 GCC 20%以上,HPC 典型气象应用 WRF 优于 GCC 10%。毕昇编译器与开源编译器SPEC CPU 2017 跑分对比毕昇编译器典型优化场景及其优化原理1 结构体内存布局优化—大幅提升缓存命中率,突破访存瓶颈SPEC CPU 2017 benchmark 中的 mcf 子项是对内存要求极高的应用,它是一款叫做MCF的大规模交通规划软件的核心代码。其瓶颈代码如下图左边所示。结构体优化原理示意图可见在 struct 中,data1 的使用率极高,而 data2 是不使用的。然而由于源代码中,数据的排布是以结构体数组的形式排布。按照一般编译器的编译方式,拿数据时每次都会将整个结构体放到 cache 里面,导致大量不参与计算的 data2 也被加载到了 cache 中,造成高速内存空间的浪费和性能的损耗。毕昇编译器会通过用户标记的结构体声明,或者通过自动检查循环中适合优化的内存场景,确认优化点。然后通过将结构体数组变为数组结构体的方式(如上图右),将有效数据紧凑排布,从而提高 cache 命中率和应用性能。经测试,此优化可以对 mcf 子项带来50%的性能提升。2 自动矢量化—计算效率提升的秘诀鲲鹏平台支持 Armv8 NEON 矢量化指令集。当前支持32个128位的矢量寄存器,指令可以同时操作4*32或2*64的数据。毕昇编译器依托这种硬件优势做了大量优化,包括 SLP(superword-level parallelism) 矢量化和循环自动矢量化。例如在 SPEC CPU 2017 benchmark 中处理视频流格式转换的x264子项中,毕昇编译器会自动识别并使用 uabd 和 udot 这类高效向量指令完成计算来替换标量指令,增大单时钟周期的数据处理量, 从而大幅提升计算效率。对于 x264 子项,这项优化可有效提升其30%的计算效率。 矢量化优化示例 3 Autotuner—基于机器学习快速获取最优编译配置如何获取性能最优编译选项是编译器使用中常见的问题,往往需要长时间的手动选项调优。为了减少这其中的工作量,使得用户能快速找到最优的优化选项,毕昇编译器自研了基于 ML 的自动搜索技术(ML-based Search) 的 Autotuner 工具。Autotuner 的调优流程由两个阶段组成:初始编译阶段(initial compilation)和调优阶段(tuning process),如下图所示:Autotuner 使用流程简单来说,在初始编译阶段,编译器会通过用户指定的调优方向,对可调优的代码区间进行标记。在随后的调优阶段,Autotuner 会根据搜索算法对不同的优化区间生成不同的编译配置。然后使用此配置编译运行,并根据运行性能的反馈来迭代优化配置参数。最后经过给定迭代次数后找出最优配置供用户使用。在实践过程中,通过 Autotuner 对 Coremark Benchmark 进行调优可以获取5%以上的收益。 以上介绍的三个优化特性分别是毕昇编译器在中前端算法优化、后端指令优化、迭代调优中最具代表性、在各自领域对性能提升表现最佳的三个特性。除以上介绍的三个优化特性之外,毕昇编译器在软件预取、循环优化、分支预测、指针压缩等编译优化技术均有探索且取得了显著的收益,详情可点击官网获取毕昇编译器详细信息,快来试试吧! 原文转载自 华为计算-【鲲鹏DevKit黑科技揭秘】┃ 毕昇编译器,让你的代码快到飞起!
  • [技术干货] 【技术长文】计算机程序和计算机语言
    什么是计算机程序 ?计算机程序是为了告诉计算机"做某件事或解决某个问题"而用"***计算机语言***编写的命令集合(语句)只要让计算机执行这个程序,计算机就会自动地、有条不紊地进行工作,计算机的一切操作都是由程序控制的,离开程序,计算机将一事无成现实生活中你如何告诉别人如何做某件事或者解决某个问题?通过人能听懂的语言: 张三你去楼下帮我买一包烟, 然后顺便到快递箱把我的快递也带上来其实我们通过人能听懂的语言告诉别人做某件事就是在发送一条条的指令计算机中也一样, 我们可以通过计算机语言告诉计算机我们想做什么, 每做一件事情就是一条指令, 一条或多条指令的集合我们就称之为一个计算机程序什么是计算机语言 ?在日常生活、工作中, 语言是人们交流的工具中国人和中国人交流,使用中文语言美国人和美国人交流,使用英文语言人想要和计算机交流,使用计算机语言可以看出在日常生活、工作中,人们使用的语言种类很多如果一个很牛人可能同时掌握了中文语言和英文语言, 那么想要和这个人交流既可以使用中文语言,也可以使用英文语言计算机其实就是一个很牛的人, 计算机同时掌握了几十门甚至上百门语言, 所以我们只要使用任何一种计算机已经掌握的语言就可以和计算机交流常见的计算机语言类型有哪些 ?机器语言所有的代码里面只有0和1, 0表示不加电,1表示加电(纸带存储时 1有孔,0没孔)优点:直接对硬件产生作用,程序的执行效率非常非常高缺点:指令又多又难记、可读性差、无可移植性汇编语言符号化的机器语言,用一个符号(英文单词、数字)来代表一条机器指令优点:直接对硬件产生作用,程序的执行效率非常高、可读性稍好缺点:符号非常多和难记、无可移植性高级语言非常接近自然语言的高级语言,语法和结构类似于普通英文优点:简单、易用、易于理解、远离对硬件的直接操作、有可移植性缺点:有些高级语言写出的程序执行效率并不高对比(利用3种类型语言编写1+1)机器语言10111000 00000001 00000000 00000101 00000001 00000000汇编语言MOV AX, 1 ADD AX, 1高级语言1 + 1————————————————版权声明:本文为CSDN博主「极客江南」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/weixin_44617968/article/details/117656810
  • [技术干货] C++入门
    一、C++概念C++是一种面向对象的计算机程序设计语言,由美国AT&T贝尔实验室的本贾尼·斯特劳斯特卢普博士在20世纪80年代初期发明并实现,最初它被称作“C with Classes”(包含类的C语言)。C++它是一种静态数据类型检查的、支持多重编程范式的通用程序设计语言,支持过程化程序设计、数据抽象、面向对象程序设计、泛型程序设计等多种程序设计风格。C++是C语言的继承,进一步扩充和完善了C语言,成为一种面向对象的程序设计语言二、C++关键字C++中总共63个关键字,包括了C语言中32个关键字三、C++命名空间在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。1.命名空间的定义定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。2.命名空间的使用C++为了防止命名冲突,把自己库里面的东西都定义在一个std的命名空间中要使用标准库里面的东西,有三种方式指定命名空间–麻烦,每个地方都要指定,但也是最规范的方式代码如下:int c = 100;namespace N{    int a = 10;    int b = 20;    int Add(int left, int right)    {        return left + right;    }    int Sub(int left, int right)    {        return left - right;    }}把std整个展开,相当于库里面的东西全部到全局域里面去了,使用起来方便但是可能会有与自己命名空间定义的冲突,规范工程中不推荐这种,日常练习可以用这种。代码如下:using namespace std;对部分常用的库里面的东西展开->针对1和2的折中方案,项目中也经常使用代码如下:using std::cout;using std::endl;int main(){    printf("%d\n", N::a);    printf("%d\n", N::b);    printf("%d\n", N::Add(1, 2));    printf("%d\n", N::Sub(1, 2));    int c = 10;    printf("%d\n", c);   //局部变量优先,所以c为10    printf("%d\n", ::c); //指定访问左边域,空白表示全局域}四、C++输入&&输出使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%costream 类型全局对象,istream 类型全局对象 ,endl全局的换行符号代码如下:struct Person{    char name[10];    int age;};int main(){    std::cout << "bit education ";    std::cout << "bit education" << std::endl;    //cout与cin对比C语言printf\scanf 来说可以自动识别类型(函数重载+运算符重载)    int a = 10;    int* p = &a;    printf("%d,%p\n", a, p);    std::cout << a << "," << p << std::endl;    std::cin >> a;    printf("%d\n", a);    char str[100];    std::cin >> str;  //cin不用&,因为引用    std::cout << str << std::endl;        struct Person P = { "uzi", 23 };  //格式化输出printf比cout好    printf("name:%s age:%d\n", P.name, P.age);    std::cout << "name:" << P.name<<" age:"<< P.age << "\n";}五、C++缺省参数1.缺省参数的概念1.缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参代码如下:void TestFunc(int a = 0){    cout << a << endl;}int main(){    TestFunc(); // 没有传参时,使用参数的默认值    TestFunc(10); // 传参时,使用指定的实参}2.缺省参数的分类半缺省参数代码如下:void testFunc3(int a, int b = 10, int c = 20){    cout << "a = " << a << endl;    cout << "b = " << b << endl;    cout << "c = " << c << endl;}全缺省参数代码如下:void testFunc2(int a = 10, int b = 20, int c = 30){    cout << "a = " << a << endl;    cout << "b = " << b << endl;    cout << "c = " << c << endl;}正常参数代码如下:void testFunc1(int a = 0){    std::cout << a << std::endl;}int main(){    testFunc1(10);    testFunc2();    testFunc3(1);    return 0;}注意:半缺省参数必须从右往左依次来给出,不能间隔着给缺省参数不能在函数声明和定义中同时出现缺省值必须是常量或者全局变量C语言不支持(编译器不支持)六、C++函数重载1.函数重载概念函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。2.函数重载实现代码如下:int Add(int left, int right){    return left + right;}double Add(double left, double right){    return left + right;}int main(){    cout << Add(10, 20) << endl;    cout << Add(10.5, 20.0) << endl;    //fun();    return 0;}注意(特别重要): 缺省参数缺省参数符合重载的定义,但如果调用的时候编译器不识别函数重载调用哪个函数,所以分情况讨论。代码如下:void fun(int a, int b, int c = 10){}void fun(int a, int b){}3.函数命名规则–>C++支持重载,C不支持为什么C++支持函数重载,而C语言不支持函数重载呢?在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。组成一个程序的每个源文件通过编译过程分别转换成目标代码(object code)。每个目标文件由链接器(linker)捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库中任何被该程序所用到的函数,而且它可以搜索程 序员个人的程序库,将其需要的函数也链接到程序中。其中编译和链接也分为几个步骤:其中分为更细的话:在C++调用Add函数在C下调用Add函数通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。4.extern "C"的作用有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译,所以这个函数不能进行重载。代码如下:extern "C" int Add(int left, int right);int main(){    Add(1, 2);    return 0;}七、C++引用1.引用的概念引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间代码如下:int main(){    int x = 10;    int &y = x;    y = 20;    std::cout << "y=" << y << std::endl;    int &z = y;    z = 30;    std::cout << "z=" << z << std::endl;}2.引用的特性引用在定义时必须初始化一个变量可以有多个引用引用一旦引用一个实体,再不能引用其他实体3.常引用代码如下:void TestConstRef(){    //常引用是创建一个临时变量,引用名是临时变量的引用    const int a = 10;    //int& ra = a; // 该语句编译时会出错,a为常量,而且a为不可以修改    const int& ra = a;    // int& b = 10; // 该语句编译时会出错,b为常量    const int& b = 10;    double d = 12.34;    //int& rd = d; // 该语句编译时会出错,类型不同    const int& rd = d;}rc是临时空间的别名代码如下:int c=10;double d=1.11;const double& rc=c;4.引用的使用场景1.做参数代码如下:void Swap2(int& a, int& b) //通过引用来交换{    int tmp = a;    a = b;    b = tmp;}void Swap1(int* a, int *b) //通过指针来交换{    int tmp = *a;    *a = *b;    *b = tmp;}2.做返回值代码如下:int& Add(int a, int b){    int c = a + b;    return c;}int main(){    int& ret = Add(1, 2);    Add(3, 4);    cout << "Add(1, 2) is :" << ret << endl;    return 0;}如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回5.传值、传引用效率比较以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。6.引用和指针的区别在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间,在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。八、C++内联函数1.内联函数概念以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。2.内联函数特性代码如下:#define _CRT_SECURE_NO_WARNINGS   1#include<iostream>int Add2(int left, int right){    return left + right;}inline int Add1(int left, int right){    return left + right;}int main(){    int ret1, ret2;    ret1 = Add1(1, 2);    ret2 = Add1(1, 2);    std::cout << ret1 << std::endl;    std::cout << ret2 << std::endl;    return 0;}inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。九、C++auto关键字1.auto关键字概念在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。2.auto关键字的使用代码如下:int main(){    int x = 10;    auto a = &x;  // int*     auto* b = &x; // int*    int& y = x;   // y的类型是什么?int    auto c = y;  // int     auto& d = x; // d的类型是int, 但是这里指定了d是x的引用    // 打印变量的类型    cout << typeid(x).name() << endl;    cout << typeid(y).name() << endl;    cout << typeid(a).name() << endl;    cout << typeid(b).name() << endl;    cout << typeid(c).name() << endl;    cout << typeid(d).name() << endl;    return 0;}auto与指针和引用结合起来使用,用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&在同一行定义多个变量当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。3.auto关键字不能使用场景auto不能作为函数的参数代码如下:// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导void TestAuto(auto a){}auto不能直接用来声明数组代码如下:void TestAuto(){  int a[] = {1,2,3};  auto b[] = {4,5,6};}为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。十、基于范围的for循环(C++11)1.范围for的语法在C++98中如果要遍历一个数组,可以按照以下方式进行:代码如下:int main(){    int array[] = { 1, 2, 3, 4, 5 };    for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)    {        cout << array[i] << " ";    }    cout << endl;}    return 0;}对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。代码如下:int main(){    // 范围for C++11新语法遍历,更简单,数组都可以    // 自动遍历,依次取出array中的元素,赋值给e,直到结束    for (auto& e : array)    {        e *= 2;    }    for (auto ee : array)    {        cout << ee << " ";    }    cout << endl;}2.范围for的使用条件for循环迭代的范围必须是确定的,对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。迭代的对象要实现++和==的操作。(关于迭代器这个问题,以后会讲,现在大家了解一下就可以了)十一、指针空值nullptr(C++11)1.程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。2.在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下3.将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。总结以上就是今天要讲的内容,本文仅仅简单介绍了C++入门的简单知识,虽然这么知识范围很大,但也为我们以后学习C++有更好的了解,我们务必掌握。另外如果上述有任何问题,请懂哥指教,不过没关系,主要是自己能坚持,更希望有一起学习的同学可以帮我指正,但是如果可以请温柔一点跟我讲,爱与和平是永远的主题,爱各位了。
  • [技术干货] 【转载技术长文】精简指令集计算机
    转自维基百科(https://zh.wikipedia.org/wiki/%E7%B2%BE%E7%AE%80%E6%8C%87%E4%BB%A4%E9%9B%86%E8%AE%A1%E7%AE%97%E6%9C%BA) 精简指令集计算机(英语:reduced instruction set computer,缩写:RISC)或简译为精简指令集,是计算机中央处理器的一种设计模式。这种设计思路可以想像成是一家模块化的组装工厂,对指令数目和寻址方式都做了精简,使其实现更容易,指令并行执行程度更好,编译器的效率更高。目前常见的精简指令集微处理器包括DEC Alpha、ARC、ARM、AVR、MIPS、PA-RISC、Power ISA(包括PowerPC、PowerXCell)、RISC-V和SPARC等。 目录 1 历史 2 精简指令集之前的设计原理 3 RISC设计原理 4 提升中央处理器性能的方法 5 参考 历史 精简指令集的名称最早来自1980年大卫·帕特森在加州大学柏克莱分校主持的Berkeley RISC计划。但在他之前,已经有人提出类似的设计理念。由约翰·科克主持,在1975年开始,1980年完成的IBM 801计划,可能是第一个使用精简指令集理念来设计的系统。 这种设计思路最早的产生缘自于有人发现,尽管传统处理器设计了许多特性让代码编写更加便捷,但这些复杂特性需要几个指令周期才能实现,并且常常不被运行程序所采用。此外,处理器和主内存之间运行速度的差别也变得越来越大。在这些因素促使下,出现了一系列新技术,使处理器的指令得以流水执行,同时降低处理器访问内存的次数。 早期,这种指令集的特点是指令数目少,每条指令都采用标准字长、执行时间短、中央处理器的实现细节对于机器级程序是可见的等等。 实际上在后来的发展中,RISC与CISC(复杂指令集)在竞争的过程中相互学习,现在的RISC指令集也达到数百条,运行周期也不再固定。虽然如此,RISC设计的根本原则——针对流水线化的处理器优化——没有改变,而且还在遵循这种原则的基础上发展出RISC的一个并发化变种VLIW(包括Intel EPIC),就是将简短而长度统一的精简指令组合出超长指令,每次运行一条超长指令,等于并发运行多条短指令。 另一方面,目前最常见的复杂指令集x86 CPU,虽然指令集是CISC的,但因对常用的简单指令会以硬件线路控制尽全力加速,不常用的复杂指令则交由微码循序器“慢慢解码、慢慢跑”,因而有“RISCy x86”之称。 精简指令集之前的设计原理 在早期的计算机业界,编译器技术并不发达,程序多半以机器语言或汇编语言完成的。为了便于编写程序,计算机体系结构师设计出越来越复杂的指令,可以直接对应高级编程语言的高级功能。当时的看法是硬件比编译器更容易设计,所以结构的复杂性在硬件这端。 加速这种复杂化的另一因素是缺乏大容量的内存。在内存容量受限的应用中,具有极高消息密度的程序更加实用。当时内存中的每一字节都很宝贵,例如只有几千个字节来存储某个完整系统。它使产业界倾向于高度编码的指令、长度不等的指令、多操作数的指令,以及把数据的搬移与计算合并在一起的指令。在当时看来,相对于使指令更容易解码,指令的编码打包问题尤为重要。 还有一个因素是当时的内存不仅容量少,而且速度很慢,使用的都是磁性技术。凭借高密度打包的指令,访问慢速资源的频率可以降低。 微处理器只有少量寄存器的两个原因是: 寄存器每一个比特位都比外部内存贵。以当时的集成电路技术水准,大量寄存器对芯片或电路板而言是难以承受的。 一旦具有大数量的寄存器,相关的指令字(opcode)将会需要更多的比特位(使用宝贵的RAM)来定位寄存器。 基于上述原因,微处理器设计师尽可能使指令做更多的工作。这导致单个指令做全部的工作:读入两个加数,相加,并将计算结果直接写入内存;另一个例子是从内存读取两个数据,但计算结果存储在寄存器内;第三个例子是从内存和寄存器各读取一个数据,其结果再次写入内存;以此类推。这种微处理器设计原理,在精简指令集(RISC)的思路出现后,最终被人称为复杂指令集。 当时设计的一个通常目标是为每个指令都提供所有的寻址模式,称为“正交性”。这给微处理器增加了一些复杂性,但理论上每个可能的命令均可单独调整。相对于使用更简单的指令,这样做能够使设计速度更快。 这类设计最终可以由功率谱的两端来表述,6502在一端,VAX在功率谱的另一端。单价25美元的1MHz 6502芯片只有一个通用寄存器,但它非常精简的单周期内存访问接口允许一个字节宽度的操作,其效率和使用更高时钟频率的设计一致,例如主频4MHz的Zilog Z80使用相同慢速的记忆芯片(大约近似300ns)。另一方面,VAX则是一种小型机,它的每个CPU至少需要三个机架来放置。其显著特点是,它支持的内存访问模式数目多得惊人,并且每条指令都可以使用任一种模式。 RISC设计原理 1970年代后期,IBM(以及其它类似企业组织)的研究人员显示,大多数正交寻址模式基本上已被程序员所忽略。这是编译器的使用逐渐增多而汇编语言的使用相对减少所导致的。值得注意的是,由于编写编译器的难度很大,当时编译器并不能充分利用CISC处理器所提供的各种特性。尽管如此,广泛应用编译器的趋势已然很明显,从而使得正交寻址模式变得更加无用。 这些复杂操作很少被使用。事实上,相比用更精简的一系列指令来完成同一个任务,用单一复杂指令甚至会更慢。这看上去有些自相矛盾,却源自于微处理器设计者所花的时间和精力:设计者一般没有时间去调整每一条可能被用到的指令,通常他们只优化那些常用的指令。一个恶名昭著的例子是VAX的INDEX指令,执行它比执行一个循环还慢。 几乎就在同时,微处理器开始比内存运行得更快。即便是在七十年代末,人们也已经认识到这种不一致性至少会在下一个十年继续增加,到时微处理器将会比内存的速度快上百倍。很明显,需要有更多寄存器(以及后来的缓存)来支持更高频率的操作。为此,必须降低微处理器原本的复杂度,以节省出空间给新增的寄存器和缓存。 不过RISC也有它的缺点。当需要一系列指令用来完成非常简单的程序时,从存储器读入的指令总数会变多,因此也需要更多时间。在当时的工业和设计领域,对RISC的性能优劣有大量持续不断的争论。 提升中央处理器性能的方法 增加寄存器的大小 增进内部的平行性 增加高速缓存大小 增加核心时脉的速度,但是此举便会导致IC从晶体管取电的功率增加,因此要遵照IC的状况予以增加核心时脉才行,此动作类似于超频。 加入其它功能,如I/O和计时器 加入向量处理器(SIMD),如VISAltiVec、SSE(Streaming SIMD Extensions) 避免附加。使朝向省电化(battery-constrained)或小型化的应用 集成多个核心 硬件多线程技术 精简指令集设计中常见的特征: 统一指令编码(例如,所有指令中的op-code永远位于同样的比特位置、等长指令),可快速解译: 泛用的寄存器,所有寄存器可用于所有内容,以及编译器设计的单纯化(不过寄存器中区分了整数和浮点数); 单纯的寻址模式(复杂寻址模式以简单计算指令序列取代); 硬件中支持少数资料类型(例如,一些CISC电脑中存有处理字节字符串的指令。这在RISC电脑中不太可能出现)。
  • [技术干货] 性能之巅:定位和优化程序CPU、内存、IO瓶颈(上)
     摘要:性能优化指在不影响系统运行正确性的前提下,使之运行得更快,完成特定功能所需的时间更短,或拥有更强大的服务能力。#一、思维导图#二、什么是性能优化?性能优化指在不影响系统运行正确性的前提下,使之运行得更快,完成特定功能所需的时间更短,或拥有更强大的服务能力。##关注不同程序有不同的性能关注点,比如科学计算关注运算速度,游戏引擎注重渲染效率,而服务程序追求吞吐能力。服务器一般都是可水平扩展的分布式系统,系统处理能力取决于单机负载能力和水平扩展能力,所以,提升单机性能和提升水平扩展能力是两个主要方向,理论上系统水平方向可以无限扩展,但水平扩展后往往导致通信成本飙升(甚至瓶颈),同时面临单机处理能力下降的问题。##指标衡量单机性能有很多指标,比如:QPS(Query Per Second)、TPS、OPS、IOPS、最大连接数、并发数等评估吞吐的指标。CPU为了提高吞吐,会把指令执行分为多个阶段,会搞指令Pipeline,同样,软件系统为了提升处理能力,往往会引入批处理(攒包),跟CPU流水线会引起指令执行Latency增加一样,伴随着系统负载增加也会导致延迟(Latency)增加,可见,系统吞吐和延迟是两个冲突的目标。显然,过高的延迟是不能接受的,所以,服务器性能优化的目标往往变成:追求可容忍延迟(Latency)下的最大吞吐(Throughput)。延迟(也叫响应时间:RT)不是固定的,通常在一个范围内波动,我们可以用平均时延去评估系统性能,但有时候,平均时延是不够的,这很容易理解,比如80%的请求都在10毫秒以内得到响应,但20%的请求时延超过2秒,而这20%的高延迟可能会引发投诉,同样不可接受。一个改进措施是使用TP90、TP99之类的指标,它不是取平均,而是需确保排序后90%、99%请求满足时延的要求。通常,执行效率(CPU)是我们的重点关注,但有时候,我们也需要关注内存占用、网络带宽、磁盘IO等,影响性能的因素很多,它是一个复杂而有趣的问题。#三、基础知识能编写运行正确的程序不一定能做性能优化,性能优化有更高的要求,这样讲并不是想要吓阻想做性能优化的工程师,而是实事求是讲,性能优化既需要扎实的系统知识,又需要丰富的实践经验,只有这样,你才能具备case by case分析问题解决问题的能力。所以,相比直接给出结论,我更愿意多花些篇幅讲一些基础知识,我坚持认为底层基础是理解并掌握性能优化技能的前提,值得花费一些时间研究并掌握这些根技术。##CPU架构你需要了解CPU架构,理解运算单元、记忆单元、控制单元是如何既各司其职又相互配合完成工作的。你需要了解CPU如何读取数据,CPU如何执行任务。你需要了解数据总线,地址总线和控制总线的区别和作用。你需要了解指令周期:取指、译指、执行、写回。你需要了解CPU Pipeline,超标量流水线,乱序执行。你需要了解多CPU、多核心、逻辑核、超线程、多线程、协程这些概念。##存储金字塔CPU的速度和访存速度相差200倍,高速缓存是跨越这个鸿沟的桥梁,你需要理解存储金字塔,而这个层次结构思维基于着一个称为局部性原理(principle of locality)的思想,它对软硬件系统的设计和性能有着极大的影响。局部性又分为时间局部性和空间局部性。### 缓存现代计算机系统一般有L1-L2-L3三级缓存。比如在我的系统,我通过进入 /sys/devices/system/cpu/cpu0/cache/index0 1 2 3目录下查看。size对应大小、type对应类型、coherency_line_size对应cache line大小。每个CPU核心有独立的L1、L2高速缓存,所以L1和L2是on-chip缓存;L3是多个CPU核心共享的,它是off-chip缓存。L1缓存又分为i-cache(指令缓存)和d-cache(数据缓存),L1缓存通常只有32K/64KB,速度高达4 cycles。L2缓存能到256KB,速度在8 cycles左右。L3则高达30MB,速度32 cycles左右。而内存高达数G,访存时延则在200 cycles左右。所以CPU->寄存器->L1->L2->L3->内存->磁盘构成存储层级结构:越靠近CPU,存储容量越小、速度越快、单位成本越高,越远离CPU,存储容量越大、速度越慢、单位成本越低。### 虚拟存储器(VM)进程和虚拟地址空间是操作系统的2个核心抽象。系统中的所有进程共享CPU和主存资源,虚拟存储是对主存的抽象,它为每个进程提供一个大的、一致的、私有的地址空间,我们gdb调试的时候,打印出来的变量地址是虚拟地址。操作系统+CPU硬件(MMU)紧密合作完成虚拟地址到物理地址的翻译(映射),这个过程总是沉默的自动的进行,不需要应用程序员的任何干预。每个进程有一个单独的页表(Page Table),页表是一个页表条目(PTE)的数组,该表的内容由操作系统管理,虚拟地址空间中的每个页(4K或者8K)通过查找页表找到物理地址,页表往往是层级式的,多级页表减少了页表对存储的需求,命失(Page Fault)将导致页面调度(Swapping或者Paging),这个惩罚很重,所以,我们要改善程序的行为,让它有更好的局部性,如果一段时间内访存的地址过于发散,将导致颠簸(Thrashing),从而严重影响程序性能。为了加速地址翻译,MMU中增加了一个关于PTE的小的缓存,叫翻译后备缓冲器(TLB),地址翻译单元做地址翻译的时候,会先查询TLB,只有TLB命失才会查询高速缓存(L1-2-3)。## 汇编基础虽然写汇编的场景越来越少,但读懂汇编依然很有必要,理解高级语言的程序是怎么转化为汇编语言有助于我们编写高质量高性能的代码。对于汇编,至少需要了解几种寻址模式,了解数据操作、分支、传送、控制跳转指令。理解C语言的if else、while/do while/for、switch case、函数调用是怎么翻译成汇编代码。理解ebp+esp寄存器在函数调用过程中是如何构建和撤销栈帧的。理解函数参数和返回值是怎么传递的。## 异常和系统调用异常会导致控制流突变,异常控制流发生在计算机系统的各个层次,异常可以分为四类:中断(interrupt):中断是异步发生的,来自处理器外部IO设备信号,中断处理程序分上下部。陷阱(trap):陷阱是有意的异常,是执行一条指令的结果,系统调用是通过陷阱实现的,陷阱在用户程序和内核之间提供一个像过程调用一样的接口:系统调用。故障(fault):故障由错误情况引起,它有可能被故障处理程序修复,故障发生,处理器将控制转移到故障处理程序,缺页(Page Fault)是经典的故障实例。终止(abort):终止是不可恢复的致命错误导致的结果,通常是硬件错误,会终止程序的执行。系统调用:## 内核态和用户态你需要了解操作系统的一些概念,比如内核态和用户态,应用程序在用户态运行我们编写的逻辑,一旦调用系统调用,便会通过一个特定的陷阱陷入内核,通过系统调用号标识功能,不同于普通函数调用,陷入内核态和从内核态返回需要做上下文切换,需要做环境变量的保存和恢复工作,它会带来额外的消耗,我们编写的程序应避免频繁做context swap,提升用户态的CPU占比是性能优化的一个目标。
  • [技术干货] 【转】如何在win10 64位下搭载汇编环境(包含汇编dosbox和masm文件)
    网上的教程只能在win10中使用debug 命令 不能使用edit命令找了半天终于找到了可以在64位机下使用的edit源文件并插入到网上下载的MASM包中。首先,将文件下载安装DOSBox0.74注意不要安装在C盘将下载的MASM文件(如图)复制到一个根目录(D或E盘)的文件夹(自己新建,比如我在E盘中创建xuyi文件夹)新文件夹的内容此时打开DosBox会出现两个程序,使用前面一个程序,但后面的不要关此时盘符是Z(为虚拟磁盘)下面使用mount命令更改DOSBOX的映射指向(注意xuyi是文件夹的名字,你输入你创建的文件夹名称)输入:mount c e:\xuyi之后按回车键当出现Drive C is mounted as local directory e:\xuyi\ 是表明映射成功下面使用dos命令”c:”更改盘符进入虚拟盘C:这个虚拟盘C就是刚才创建的文件夹xuyi接下来就可以愉快得进行汇编程序设计啦!!!下图是使用debug中的-r命令使用edit命令注意点进去后可能光标会消失,此时调用任务管理器alt+ctrl+del即可释放光标。转自:https://blog.csdn.net/xyisv/article/details/69062382?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.compare 
  • [交流分享] gcc和mingw的区别
    ## Mingw MinGW全称Minimalist GNU For Windows,是个精简的Windows平台C/C++、ADA及Fortran编译器,相比Cygwin而言,体积要小很多,使用较为方便。MinGW提供了一套完整的开源编译工具集,以适合Windows平台应用开发,且不依赖任何第三方C运行时库。 MinGW包括: 一套集成编译器,包括C、C++、ADA语言和Fortran语言编译器 用于生成Windows二进制文件的GNU工具的(编译器、链接器和档案管理器) 用于Windows平台安装和部署MinGW和MSYS的命令行安装器(mingw-get) 用于命令行安装器的GUI打包器(mingw-get-inst) ## GCC GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器。它是以GPL许可证所发行的自由软件,也是 GNU计划的关键部分。GCC原本作为GNU操作系统的官方编译器,现已被大多数类Unix操作系统(如Linux、BSD、Mac OS X等)采纳为标准的编译器,GCC同样适用于微软的Windows。 GCC是自由软件过程发展中的著名例子,由自由软件基金会以GPL协议发布。 GCC 原名为 GNU C 语言编译器(GNU C Compiler),因为它原本只能处理 C语言。GCC 很快地扩展,变得可处理 C++。后来又扩展能够支持更多编程语言,如Fortran、Pascal、Objective-C、Java、Ada、Go以及各类处理器架构上的汇编语言等,所以改名GNU编译器套件(GNU Compiler Collection)。 ## 总结 mingw可以理解为gcc在windows平台下的实现。但是MinGW使用Windows中的C运行库,因此用MinGW开发的程序不需要额外的第三方DLL支持就可以直接在Windows下运行,而且也不一定必须遵从GPL许可证;这同时造成了MinGW开发的程序只能使用Win32API和跨平台的第三方库,而缺少POSIX支持,大多数GNU软件无法在不修改源代码的情况下用MinGW编译。
  • [技术干货] 【转载】C 与 C++ 40 年的爱恨情仇
    以下为译文:70年代初,贝尔实验室创建了C语言,它是开发UNIX的副产品。很快C就成为了最受欢迎的编程语言之一。但是对于Bjarne Stroustrup来说,C的表达能力还不够。于是,他在1983年的博士论文中扩展了C语言。于是,支持类的C语言诞生了。当时,Bjarne Stroustrup明白编程语言有许多组成部分,除了语言本身,还有编译器、链接器和各种库。提供熟悉的工具有助于语言被广泛接受。在这种历史背景下,在C语言的基础上开发C++也是有道理的。40年后,C和C++都在行业中得到了广泛使用。但是,互联网上的C开发人员认为C++是有史以来最糟糕的人类发明,而许多C++开发人员则希望有朝一日C语言灰飞烟灭。究竟发生了什么事?从表面上看,C和C++都可以满足相同的用例:高性能、确定性、原生但可移植的代码,可用于最广泛的硬件和应用程序。但是,更让C自豪的是它是一门低级语言,更接近汇编。而C++,从诞生第一天开始就充斥了各种奇怪的东西。例如析构函数这个黑魔法。自作主张的编译器。尽管很早C++就有了类型推断功能,但是80年代中期的开发人员还无法接受这个概念,因此Bjarne Stroustrup不得不删除了auto,直到C++ 11又重新添加回来。从那以后,C++就不断加入各种工具来实现抽象。很难说C++是一种低级语言还是高级语言。从设计目的上来说,C++两者都是。但是在不牺牲性能的情况下,建立高级抽象是很困难的。于是C++引入了各种工具来实现constexpr、move语义、模板和不断增长的标准库。从根本上讲,我认为C信任开发人员,而C++信任编译器。这是一个巨大的差异,单凭“两者的原生类型相同”、“while循环的语法相同”等简单一致是无法掩盖的。C++开发人员将有这些问题归咎于C,而C开发人员则认为C++过于疯狂。我觉得站在C的角度看C++,这种说法也很正确。作为C的超集,C++确实很疯狂。一个经验丰富的C开发人员面对C++可能没有熟悉的感觉。C++不是C,这就足以引发互联网上的激烈争论。然而,虽然我不喜欢C,但也没有权利取笑C。尽管我有一定的C++经验,但用C编写过的代码少之又少,而且肯定是很糟糕的代码。好的编程语言包括良好的实践、模式、惯用写法,这些都需要多年的学习。如果你尝试用编写C++的方式写C的代码,或者用C的方式编写C++的代码,那感觉一定很糟糕。即便你懂C,也不一定会C++,反之亦然,懂C++也不一定会用C编程。那么,我们是否应该停止说C/C++,为这两个不幸的命名而感到悲哀吗?也不至于。尽管C++的设计理念与C不一样,但是C++仍然是C的超集。也就是说,你可以在C++转换单元中包含C的头文件,这样依然可以通过编译。而这正是造成混乱的地方。C++不是C的扩展,它是由不同的委员会、不同的人独立设计的标准。从逻辑上讲,喜欢C++理念的人会参与C++社区以及C++标准化的过程,而其他人可能会尝试参与C。无论是C的委员会还是C++委员会,他们表达意图和方向的方式只能通过各自的最终产品:标准;而标准是众多投票的成果。然而,编译器很难知道它正在处理的是C头文件还是C++头文件。extern “C” 标记并没有得到广泛一致的使用,而且它只能影响修饰,而不会影响语法或语义。头文件仅对预处理器有影响,对于C++编译器而言,所有内容都是C++转换单元,因此也就是C++。然而,人们依然会在C++中包含C头文件,并期望它“正常工作”,而大多数时候也确实可以正常工作。那么,我们不禁想问:由不同地方的、不同的人开发的C++代码如何保持C的兼容性?恐怕很难。最近,一位同事让我想起了康威定律:"设计系统的架构受制于产生这些设计的组织的沟通结构。"根据这个逻辑,如果两个委员不互相合作,则他们创造的语言也不会互通。C++维护了一个与C及其标准库的不兼容列表。然而该列表似乎并未反映出许多C11和C18中添加、但在C++中不合法的功能。更清晰的介绍请参见这个维基本科页面(https://en.wikipedia.org/wiki/Compatibility_of_C_and_C%2B%2B)。然而,仅仅列出两种语言之间的不兼容性,并不足以衡量二者的不兼容性。那些存在于C++标准库中但主要声明来自C的函数,很难声明成constexpr,更难声明成noexcept。C的兼容性会导致性能成本,而C函数是优化的障碍。许多C的结构在C++中都是有效的,但无法通过代码审查(如NULL、longjmp、malloc、构造/析构函数、free、C风格的类型强制转换等)。在C看来,这些惯用写法可能问题不大,但在C++中可不行。C++具有更强大的类型系统,不幸的是,C的惯用写法在这个类型系统中凿了一个洞,因此实现C的兼容性需要在安全性方面付出代价。别误会,C++仍然关心C的兼容性,某种程度上。然而,有趣的是C也很关心C++,某种程度上。实话实说,C对C++的关心程度可能高于C++对C的关心。看来,每个委员会还是在乎另一个委员会的工作。但我们很不情愿。C++知道,许多基础库都是用C编写的,不仅包括libc,而且还有zip、png、curl、openssl(!)以及许多其他库,无数的C++项目都在使用这些库。C++不能破坏这些兼容性。但是最近,尤其是在过去的十年中,C++的规模已远远超过C。C++拥有更多的用户,并且社区更加活跃。也许这就是为什么如今C++委员会的规模是C委员会的10倍以上。C++是不可忽视的力量,因此C委员会必须考虑不破坏C++兼容性。如果非要说一个标准追随另一个标准对话,那么如今C++是领头者,而C是追随者。现在,C++处于稳定的三年周期中,无论是风雨还是烈日,抑或是致命的新疫情。而C每十年左右才发布一次主版本。不过这也很合理,因为作为一种较低级的语言,C不需要发展得那么快。C语言的环境也与C++完全不同。C多用于平台,更多地用于编译器。每个人(甚至他们的狗狗)都会编写C编译器,因为该语言的特性集很小,所以任何人都可以编写C编译器。而C++委员会真正考虑的实现只有四种,而且在每次会议上这四种实现都会出现。所以,C语言中的许多功能都是与实现有关的,或者是可选支持的,这样各种编译器不需要做太多努力就可以声称自己遵从了标准,据说这样委员会的人会比较高兴。如今,C++更加侧重于可移植性,而不是实现的自由。这又是一个理念的不同。因此,你的提议破坏了C的兼容性我提议的P2178的一部分理论上会影响与C的兼容性。这样的话所有方案都不会令人满意。有人可能会说,你可以先向C委员会提议你的新特性。这意味着需要召开更多会议。C会议的严格出席规则可能导致你无法参加会议,这就将那些不愿意花上数千美元成为ISO会员的个人拒之门外。这是因为C委员会必须遵守ISO的规则。而且,如果新的标准刚刚发布,那么可能还需要等待十年时间,你的提案才会被考虑。最重要的是,如果C委员不理解或不在乎你正在努力解决的问题,那么你的提案就石沉大海了。或者他们可能没有精力来处理这个问题。而且,可能你也没有精力来处理C。毕竟,你的本意是要改进C++。实际上,哪怕会议上无人反对你的提议(尽管不太可能发生),如果有人让你先去跟C委员会的人讨论,就等于给你的提议判了死刑。另一种可能的情况是,C委员会接受与C++中存在的版本略有不同的版本。true只能做一个宏来实现。char16_t需要通过typedef。char32_t不一定是UTF-32。static_assert对应的是 _Static_assert。这类的情况还有很多,我们应该责备C吗?可能不应该。他们的委员会只是在尽力将C语言做好。反之亦然。在C++20中,指定的初始化器就受到了C的启发,但采取了略微不同的规则,因为如果完全一样的话就不符合C++的初始化规则。对于这个问题,我也有责任。C有VLA。如果当时我在,我一定会反对在标准C++中采用它,因为它导致了太多安全性问题。我也会坚决反对将_Generic添加到C++中的提议。也许_Generic的目的是减少由于缺乏模板或缺乏重载而导致的问题,但是C++有这两个功能,从我的角度来看,_Generic并不适合我想象中的C++。这两个委员会似乎对于对方语言的关心程度也不一样。有时我们会遇到兼容性非常好的情况(std::complex),有时完全不在乎兼容性(静态数组参数)。这没有办法。别忘了每个委员会都是一群人,他们在不同的时间、不同的地点投票,而试图控制结果会导致投票毫无意义。将这些人放在同一个房间也不现实。ISO可能会反对,参与者的不平衡会导致C的人处于极大的劣势。C的兼容性不重要如果你是C开发人员,那么肯定会把C视为一种简洁的编程语言。但对于我们其他人而言,C的印象完全不同。C是通用的、跨语言的胶水,可以将一切紧密地结合在一起。对于C++用户而言,C就是他们的API。从这一点来看,C的价值在于其简单性。请记住,C++关心的那一部分C是出现在接口(头文件)中的C。我们关心的是声明,而不是定义。C++需要调用C库中的函数(Python、Fortran、Rust、D、Java等语言也一样,在所有情况下都可以在接口边界使用C)。因此,C是一种接口定义语言。向C添加的内容越多,定义接口就越困难。这些接口随着时间的推移保持稳定的可能性较小。那么,C++中缺少<threads.h>是否重要?可能并不重要,因为这不太可能出现在公共接口中。如今大家都在谈论C过去,C的兼容性是C++的一大卖点。但如今,每个人(甚至他们的金鱼)都懂C。Rust可以调用C函数,Python、Java、一切语言都可以!甚至怪异的Javascript都可以在WebAssemby中调用C函数。但是在这些语言中,接口是显式的。该语言提供的工具可以公开特定的C声明。当然,这比较麻烦。但这可以让接口非常非常清晰。而且还是有界的。例如,在rust中,调用C函数并不会迫使Rust牺牲某些设计来容纳C子集。实际上C是被包含进去的。mod confinment {use std::os::raw::{c_char};extern "C" {pub fn puts(txt: *const c_char);}}pub fn main() {unsafe {confinment::puts(std::ffi::CString::new("Hello, world!").expect("failed!").as_ptr());}}
  • [技术干货] 2020-09-28:内存屏障的汇编指令是啥?
    2020-09-28:内存屏障的汇编指令是啥?#福大大架构师每日一题#