sizeof陷阱分析
- 格式:doc
- 大小:170.50 KB
- 文档页数:21
C语⾔之陷阱与缺陷详解⽬录⼀、前⾔⼆、字符指针三、边界计算与不对称边界1.经典错误①2.经典错误②3、⼩结四、求值顺序五、运算符&& ||和!总结⼀、前⾔⼆、字符指针结论⼀:复制指针并不会复制指针所指向的内容。
两个指针所指向位置相同,实际为同⼀个指针。
结论⽽:开辟两个数组,即使两个数组内容相同,地址也绝不相同。
三、边界计算与不对称边界1.经典错误①int main(){int i = 0; int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };for (i = 0; i < 13; i++){arr[i] = 0;printf("haha");}return 0;}计算的结果是程序陷⼊死循环分析:1.栈区默认先使⽤⾼地址,再使⽤低地址2.数组内元素随下标增长,地址由低到⾼变化调试后即可发现,i与arr[9]的地址相差3字节,所以i即为实际不存在的arr[12].[补充知识:ANSI c标准允许这种⽤法——数组中溢界元素的地址位于数组所占内存之后,这个地址可以进⾏赋值和⽐较,但是不能解引⽤(若是数组之前存在溢界则语法不允许)] 2.经典错误②⼗⽶长的围栏每⼀⽶就需要⼀根栏杆⽀撑,则共需要⼏根栏杆? 113、⼩结栏杆问题你若不假思索可能会回答为10。
栏杆问题的根源正是加减⼀带来的困惑对此我们坚持以下原则原则⼀:考虑最简单的特例(如考虑20到10间有⼏个数,20-10还要+1吗。
不妨考虑10到10有⼏个数)原则⼆:仔细计算边界⽽在实际编程中,⼀个编程技巧则可以"⼀⾔以蔽之",即不对称边界。
x>=0 && x<16 要优于 x>=0 && x<=15不对称边界上界-下界就是之间所包含的数。
四、求值顺序总结:c语⾔中只有四个运算符(&& ;|| ;?: ;,)明确规定了求值顺序&&和||先对左边求值,只在需要时对右边求值:if(y!=0 && x/y>a)如此避免除0错误。
编程技术中常见的陷阱和避免方法在编程技术领域,常常会遇到一些陷阱,这些陷阱可能会导致代码错误、性能问题或安全漏洞。
为了写出高质量的代码,我们需要了解这些陷阱,并采取相应的避免方法。
本文将介绍一些常见的编程技术陷阱以及如何避免它们。
一、空指针异常空指针异常是编程中最常见的错误之一。
当我们在代码中使用一个空指针时,程序会抛出NullPointerException。
为了避免这个问题,我们可以在使用指针之前进行空指针检查,或者使用安全调用操作符(?.)来避免空指针异常的发生。
例如,在Java中,我们可以使用以下方式来避免空指针异常:```javaif (object != null) {// 执行操作}```或者```javaobject?.method();```二、内存泄漏内存泄漏是指程序中分配的内存空间没有正确释放,导致内存占用不断增加,最终耗尽系统资源。
为了避免内存泄漏,我们需要注意及时释放不再使用的内存。
在使用动态内存分配时,我们应该确保在不再使用某个对象时,及时调用释放内存的方法,如delete、free等。
此外,我们还可以使用自动垃圾回收机制来自动释放内存,例如Java中的垃圾回收器。
三、缓冲区溢出缓冲区溢出是指向一个缓冲区写入超过其容量的数据,导致覆盖其他内存区域。
这种错误很容易被黑客利用,造成安全漏洞。
为了避免缓冲区溢出,我们需要确保在写入数据时,不超过缓冲区的容量。
在C/C++中,我们可以使用安全的字符串处理函数,如strcpy_s、strncpy_s等,这些函数会检查缓冲区的容量,避免溢出。
在其他编程语言中,也可以使用类似的函数或方法来保证数据的安全写入。
四、死锁死锁是指两个或多个进程无限期地等待对方所持有的资源,导致程序无法继续执行。
为了避免死锁,我们需要注意合理地管理资源的使用。
在多线程编程中,我们可以使用同步机制,如互斥锁、条件变量等,来避免死锁的发生。
同时,我们还应该避免使用过多的锁,尽量减少锁的粒度,以降低死锁的概率。
第一章32个关键字简记注:该笔记根据《C语言深度剖析》一书而成sizeof是32个关键字之一什么是声明?什么是定义?详见P11页定义与声明的区别:定义创建了对象并为这个对象分配了内存,声明没有分配内存,可以先声明把位子占了(如函数的声明),然后再定义auto关键字(P12):可以当其不存在,默认情况下所有变量都是auto型的register关键字(P12):请求编译器尽可能的将变量存储在cpu内部的寄存器中,是尽可能不是绝对,一个cpu的寄存器也就几个或者几十个,存取速度最快register变量必须是一个单个的值,而且其长度最长为一个整型的长度。
对这种变量不可以使用取址运算符来获取其地址,因为它不再内存中static关键字(P13):可以修饰变量也可以修饰函数、存储区为内存中的静态存储区、修饰函数时是为了将该函数的作用域局限于本文件内,解决同名问题例:#include<stdio.h>#include<stdlib.h>static int j;void fun1(void){static int i = 0;i++;printf("%d\t",i);}void fun2(void){j = 0 ;j++;}int main(){int k;for(k = 0; k < 10;k++){fun1();fun2();}printf("\n j = %d\n",j);//此处的i不能输出,因为fun1()函数的返回值是void型,static int i 的作用域为整个fun1()函数内,超出无效//程序完结时, i=10; j=1;return EXIT_SUCCESS;}》》》》 1 2 3 4 5 6 7 8 9 10j = 1C语言的数据类型:基本数据类型关键字(P14):short、long、int、char、float、double(共6种)构造数据类型:数组、结构体struct、公用体union、枚举类型enum指针类型空类型void32位系统中:short为2个字节,int为4个字节,long为4个字节,float为4个字节、 double为8个字节,char为1个字节(一般情况下)隐式类型转换:short,char →→ int →unsigned int →long →double(主干道)←←float char+char 是先隐式转换为int的,再进行运算任何数据都的先跑到主干道,才能运算(int到double为主干道,32位的问题)变量名的命名规则(P15):注意x与X之间,1(数字1)与l(字母l)之间,0与o之间的区别,不建议使用这些易混淆的定义了变量的同时千万别忘了对其初始化不同数据之间的运算要注意精度扩展问题,一般是低精度数据向高精度数据扩展sizeof关键字(P19):例:int i = 0;A sizeof(int)B sizeof(i)C sizeof intD sizeof i毫无疑问,在32位系统下A、B的值为4,其实D也是4注:函数名后面没有括号是绝对不行的,编译C时,编译器会提示出错我们可以在int前加unsight、const等关键字但是不能加sizeof关键字sizeof在计算变量的大小时括号可以省略但是在计算类型的大小时括号是不可以省略的sizeof (int)*p 是什么意思?个人认为相当于是sizeof((int)*p)例:#include<stdio.h>#include<stdlib.h>void fun(int b[100]){printf("\nsizeof(b) = %d\n",sizeof(b));}int main(){int *p = NULL;printf("sizeof(p) = %d\n",sizeof(p));printf("sizeof(*p) = %d\n",sizeof(*p));int a[100];printf("\nsizeof(a) = %d\n",sizeof(a));//此处的大小为100,其余均为四printf("sizeof(a[100]) = %d\n",sizeof(a[100]));//a[100]存在吗??越了界还是按4累加?printf("sizeof(&a) = %d\n",sizeof(&a));printf("sizeof(&a[0]) = %d\n",sizeof(&a[0]));int b[100];fun(b);return EXIT_SUCCESS;}》》》sizeof(p) = 4sizeof(*p) = 4sizeof(a) = 400sizeof(a[100]) = 4sizeof(&a) = 4sizeof(&a[0]) = 4sizeof(b) = 4--------------------------------------------------------------------- signed、unsigned关键字(P20):在计算机系统中,任何数据到了底层都会被转化成0、1 在缺省的情况下,系统默认为是singed型在计算机系统中数值用补码存储的原因是:使用补码,可以将符号位与其他位统一处理,同时减法也可以按加法处理。
看完《C陷阱与缺陷》,忍不住要重新翻一下,记录一下与自己的惯性思维不符合的地方。
记录的是知识的增量,是这几天的流量,而不是存量。
这本书是在ASCI C/C89订制之前写的,有些地方有疏漏。
第一章词法陷阱1.3 C语言中解析符号时使用贪心策略,如x+++++y将被解析为x++ ++ +y,并编译出错。
1.5 单引号引起的一个字符代表一个对应的整数,对于采用ASCII字符集的编译器而言,'a'与0141、97含义一致。
练习1.1 嵌套注释(如/*/**/*/)只在某些C编译器中允许,如gcc4.8.2编译时是不支持的。
第二章语法陷阱∙ 2.6 else始终与同一个括号内最近的未匹配的if结合第三章语义陷阱∙ 3.1 int a[12][31]表示的是一个长度12的数组,每个元素是一个长度31的数组。
∙ 3.1 在需要指针的地方如果使用数组名来替换,那么数组名就被视为其下标为0的元素的指针,p = &a的写法是非法的(gcc4.8.2只是警告)。
∙ 3.2 如何连接两个给出的字符串s、t?细节很重要,书中给出的答案如下:char *r,*malloc()//原文称不能直接声明一个s、t长度之和的数组,但c99可以声明变长数组,已经可以了//记得要把长度加1r = malloc(strlen(s) + strlen(t) +1);//必须判断内存是否分配成功if(!r){complain();exit(1);}strcpy(r,s);strcat(r,t);......//完成之后一定要释放rfree(r);∙ 3.6 如何正确计算数组的边界?原则一,考虑最简单情况下的特例;原则二,仔细计算边界。
∙ 3.6 以下一段代码为何引起死循环?这是因为在内存地址递减时,a[10]就是i。
int i,a[10];for(i = 1; i<=10; i++)a[i] = 0;∙∙ 3.6 边界的编程技巧:用第一个入界点和第一个出界点表示数值范围,即[low,high)。
CC++中的算术及其陷阱⽬录概述⽆符号数和有符号数是通⽤的计算机概念,具体到编程语⾔上则各有各的不同,程序员是解决实际问题的,所以必须熟悉编程语⾔中的整数。
C/C++ 有⾃⼰特殊的算术运算规则,如整型提升和寻常算术转换,并且存在⼤量未定义⾏为,⼀不⼩⼼就会产⽣ bug,解决这些 bug 的最好⽅法就是熟悉整数性质以避免 bug。
我不是语⾔律师(⾮贬义),对 C/C++ 算术特性的了解主要来⾃教材和互联⽹,但基本上都查阅 C/C++ 标准验证过,C 和 C++ 在整数性质和算术运算上应该是完全相同的,如果有错误请指正。
C/C++ 整数的阴暗⾓落C/C++ 期望⾃⼰可以在所有机器上运⾏,因此不能在语⾔层⾯上把整数的编码、性质、运算规定死,这让 C/C++ 中存在许多未规定的阴暗⾓落和未定义⾏为。
许多东西依赖于编译器、操作系统和处理器,这⾥通称为运⾏平台。
标志没有规定整数的编码,具体的编码格式依赖于运⾏平台。
char有⽆符号依赖于运⾏平台,编译器有选项可以控制,如 GCC 的 -fsign-char。
移位⼤⼩必须⼩于整数宽度,否则是未定义⾏为。
⽆符号数左移 K 位结果为原来的 2^K 次⽅,右移 K 位结果为原来的数除 2^K 次⽅。
仅允许对值⾮负的有符号数左移右移,运算结果同上,对负数移位是未定义的。
标准仅规定了标准内置整数类型(如int等)的最⼩宽度和⼤⼩关系(如long不能⼩于int),但未规定具体⼤⼩,如果要⽤固定⼤⼩的整数,请使⽤拓展整数类型(如uint32_t)等。
⽆符号数的溢出是合法的,有符号数溢出是未定义⾏为整型字⾯量常常有⼈说 C/C++ 中的整数字⾯量类型是int,但这种说法是错误的。
C/C++ 整形字⾯量究竟是什么类型取决于字⾯量的格式和⼤⼩。
StackOverflow 上有⼈问,代码⽚段如下:if (-2147483648 > 0) {std::cout << "true";} else {std::cout << "false";}现在让我们来探索为什么负数会⼤于 0。
C语言陷阱和缺陷[1]原著:Andrew Koenig - AT&T Bell Laboratories Murray Hill, New Jersey 07094原文:收藏翻译:lover_P[译序]那些自认为已经“学完”C语言的人,请你们仔细读阅读这篇文章吧。
路还长,很多东西要学。
我也是……[概述]C语言像一把雕刻刀,锋利,并且在技师手中非常有用。
和任何锋利的工具一样,C会伤到那些不能掌握它的人。
本文介绍C语言伤害粗心的人的方法,以及如何避免伤害。
[内容]·0 简介· 1 词法缺陷o 1.1 =不是==o 1.2 &和|不是&&和||o 1.3 多字符记号o 1.4 例外o 1.5 字符串和字符· 2 句法缺陷o 2.1 理解声明o 2.2 运算符并不总是具有你所想象的优先级o 2.3 看看这些分号!o 2.4 switch语句o 2.5 函数调用o 2.6 悬挂else问题· 3 链接o 3.1 你必须自己检查外部类型· 4 语义缺陷o 4.1 表达式求值顺序o 4.2 &&、||和!运算符o 4.3 下标从零开始o 4.4 C并不总是转换实参o 4.5 指针不是数组o 4.6 避免提喻法o 4.7 空指针不是空字符串o 4.8 整数溢出o 4.9 移位运算符· 5 库函数o 5.1 getc()返回整数o 5.2 缓冲输出和内存分配· 6 预处理器o 6.1 宏不是函数o 6.2 宏不是类型定义·7 可移植性缺陷o7.1 一个名字中都有什么?o7.2 一个整数有多大?o7.3 字符是带符号的还是无符号的?o7.4 右移位是带符号的还是无符号的?o7.5 除法如何舍入?o7.6 一个随机数有多大?o7.7 大小写转换o7.8 先释放,再重新分配o7.9 可移植性问题的一个实例·8 这里是空闲空间·参考·脚注0 简介C语言及其典型实现被设计为能被专家们容易地使用。
至从C语言开始enum类型就被作为用户自定义分类有限集合常量的方法被引入到了语言当中,而且一度成为C++中定义编译期常量的唯一方法(后来在类中引入了静态整型常量)。
根据上面对enum类型的描述,到底enum所定义出来的类型是一个什么样的类型呢?作为一个用户自定义的类型其所占用的内存空间是多少呢?使用enum类型是否真的能够起到有限集合常量的边界约束呢?大家可能都知道enum类型和int类型具有隐示(自动)转换的规则,那么是否真的在任何地方都可以使用enum类型的变量来代替int类型的变量呢?下面会逐一回答这些问题。
1.到底enum所定义出来的类型是一个什么样的类型呢?在C++中大家都知道仅仅有两种大的类型分类:POD类型和类类型(不清楚的可以参见我的其他文章)。
enum所定义的类型其实属于POD类型,也就是说它会参与到POD类型的隐示转换规则当中去,所以才会出现enum类型与int类型之间的隐示转换现象。
那么也就是说enum所定义的类型不具备名字空间限定能力(因为不属于类类型),其所定义的常量子具备和enum类型所在名字空间相同的可见性,由于自身没有名字限定能力,所以会出现名字冲突现象。
如:struct CEType{enum EType1{e1,e2};enum EType2{e1,e2};};上面的例子会出现e1、e2名字冲突编译时错误,原因就在于枚举子(e1、e2)是CEType名字空间中的名字,同样在引用该CEType中的枚举子时必须采用CEType::e1这样的方式进行,而不是CEType::EType1::e1来进行引用。
2.作为一个用户自定义的类型其所占用的内存空间是多少呢?该问题就是sizeof(EType1)等于多少的问题,是不是每一个用户自定义的枚举类型都具有相同的尺寸呢?在大多数的32位编译器下(如:VC++、gcc等)一个枚举类型的尺寸其实就是一个sizeof(int)的大小,难道枚举类型的尺寸真的就应该是int类型的尺寸吗?其实不是这样的,在C++标准文档(ISO14882)中并没有这样来定义,标准中是这样说明的:“枚举类型的尺寸是以能够容纳最大枚举子的值的整数的尺寸”,同时标准中也说名了:“枚举类型中的枚举子的值必须要能够用一个int类型表述”,也就是说,枚举类型的尺寸不能够超过int类型的尺寸,但是是不是必须和int类型具有相同的尺寸呢?上面的标准已经说得很清楚了,只要能够容纳最大的枚举子的值的整数就可以了,那么就是说可以是char、short和int。
1 C++ sizeof 使用规则及陷阱分析 1、什么是sizeof 首先看一下sizeof在msdn上的定义: The sizeof keyword gives the amount of storage, in bytes, associated with a variable or a type (including aggregate types). This keyword returns a value of type size_t. 看到return这个字眼,是不是想到了函数?错了,sizeof不是一个函数,你见过给一个函数传参数,而不加括号的吗?sizeof可以,所以sizeof不是函数。网上有人说sizeof是一元操作符,但是我并不这么认为,因为sizeof更像一个特殊的宏,它是在编译阶段求值的。举个例子:
cout cout<<4 int a = 0; cout int a = 0; cout<<4 int i = 2; cout 结论:不论sizeof要对谁取值,最好都加上()。 3、数据类型的sizeof (1)C++固有数据类型 32位C++中的基本数据类型,也就char,short int(short),int,long int(long),float,double, long double 大小分别是:1,2,4,4,4,8, 10。 考虑下面的代码: cout typedef short WORD; typedef long DWORD; cout<<(sizeof(short) == sizeof(WORD)) int f1(){return 0;}; double f2(){return 0.0;} void f3(){} cout cout 指针是f8888(32位,相当于段地址*16 + 位移,但寻址范围要更大)。 5、数组问题 考虑下面问题: char a[] = "abcdef"; int b[20] = {3, 4}; char c[2][3] = {"aa", "bb"}; cout int *d = new int[10]; cout double* (*a)[3][6]; cout #include using namespace std; int Sum(int i[]) { int sumofi = 0; 4 for (int j = 0; j < sizeof(i)/sizeof(int); j++) //实际上,sizeof(i) = 4 { sumofi += i[j]; } return sumofi; } int main() { int allAges[6] = {21, 22, 22, 19, 34, 12}; cout int Sum(int (*i)[6]) { int sumofi = 0; for (int j = 0; j < sizeof(*i)/sizeof(int); j++) //sizeof(*i) = 24 { sumofi += (*i)[j]; } return sumofi; } int main() { int allAges[] = {21, 22, 22, 19, 34, 12}; cout int Sum(int (&i)[6]) { int sumofi = 0;