C-C++到底支不支持VLA以及两种语言中const的区别
C-C++到底支不支持VLA以及两种语言中const的区别
到底支不支持VLA
VLA就是variable-length array,也就是变长数组。
最近写程序的时候无意间发现,gcc中竟然支持下面这种写法:
int n = 10;
int a[n];
注意上面的语句是在函数内部写的,也就是n和a都是自动变量。
当时十分疑惑,C语言中数组的长度不应该是常量或常量表达式吗?为什么变量也可以。我将代码在VC中跑了一下,发现编译出错,提示数组的大小未知,说明VC中是不支持VLA的。
那既然有的编译器支持VLA,又有的编译器不支持VLA,那么C标准到底是怎样规定的呢?然后我看是看书、在网上查资料。
C Primer Plus一书中是这样描述的:
C90标准中并不支持VLA,C99开始支持VLA,很大的一个原因:FORTRAN中支持这种写法。C99中对对VLA有一些限制,比如变长数组必须是自动存储类型,也就是说,如果我上面两句放在函数外面就就不能通过编译了,这是因为在函数外面定义的是全局变量,此外,使用VLA不能对数组进行初始化,因为它的长度在运行时才能确定。
此外VLA并不是真正的变长,它实际上只是将数组的长度推迟到运行时确定而已,也就是说C90标准中,数组的长度必须在编译时期就知道,但C99支持VLA后,数组的长度可以推迟到运行时知道,但是,一旦长度确定,数组的长度就不能变了。
此外,网上的大神说,C++的标准中无论是C++90还是C++99还是C++11都不支持VLA的这种写法。
鉴于以上原因,在C语言中,如果想用变长的数组,还是老老实实用malloc分配吧,在C++中当然有更好的选择,就是vector,当然C++11中又推出了一个array,而且这两种都是真正的变长,也就是数组的长度随时都可以改变。
下面我还想说一下C和C++中const关键字的区别。
const关键字最早是C++中的产物,后来才引入到C语言中。const在C语言中和在C++中是相当不一样的。
在C语言中const修饰的,被认为是一个只读的、或者叫不可改变的变量,它实际上只是一个变量,只不过对这个变量做了一些限制。而C++中const修饰的才是真正的常量。当然这其中还有很多细节的地方,有些地方我也还有些模糊,下面,我就通过例子,把自己已经理解的东西写出来。
先来一个例子:
const int a = ; int array[a]; int main() { return ; }
用gcc和g++编译的结果分别如下:
可以看到gcc下不能通过编译,但是g++下可以通过,说明C语言中有错,在C++中没错。
原因解释:
首先说C语言中:
首先说明,即使在支持VLA的编译器下,(我的gcc是支持的),前面提到了VLA数组是有限制的,VLA必须是自动存储类型,而上面的代码中数组是全局变量,所以并不存在是否支持VLA的问题。上面提到,在C语言中const被认为是一个受到一定限制的变量,是变量就要被分配数据区(或者运行时的栈区等)内存空间,由于a是全局变量,全局变量位于数据区,空间在编译时期就分配了。而,需要注意,编译时期,编译器是不能读取数据区的内存的(它可以分配数据区的内存,并初始化内存,但是不能从数据区的牛叉女内存中读取数据)。所以在编译时期,编译器其实并不知道a的值是什么,因为它不能读数据区的内存而a的值是在内存中的。但是,对于数组array编译器是一定要知道数组的长度才行的,也就是必须要知道a的值,这样就矛盾了,所以编译器就报错了!
那在C++中有为什么能够通过呢?
原因就是C++真的把const当成常量看待。
详细解释一下:
const int a = 10;这条语句中10是我们所说的字面量,无论是在C中还是在C++中字面量都是保存在代码段中,编译初期会将其保存在符号表中。C++尽量不对const分配数据区(或者运行时的栈区)的内存空间,只在必须分配内存时才分配(这个后面再说)。下面一条语句int array[a],编译器一定要知道a的值的,C语言要想知道a的值,必须读内存,但是C++却不需要,直接读取代码段中的的符号表即可,编译时期访问符号表是没有任何问题的,但是访问数据区的内存是做不到的。所以上面的语句在C++中是没有问题的。
再来说说,什么叫尽可能不为const分配内存。
如果代码是这样
const int a = ; const int *p = &a; int array[a]; int main() { return ; }
注意 const int *p = &a;这句,对a取地址操作,我们知道位于代码段的数据是不取地址的,所以这个时候,只能给a在数据区分配空间了。
于是就出现了新的问题,既然给a分配了数据区的空间,那是不是编译时期就不知道a的值了,因为毕竟编译时期是不能读取数据区的内存的,那么后面数组的定义也就不行了吧?但是答案却相反,依然可以,这是因为当编译器读a的值的时候,不是从数据区的内存中,而是程序段的符号表中读取的那个字面常量10。所以在编译实际依然能够确定数组的长度。
下面的例子应该更能说明这个问题:
#include <stdio.h> int main() { const int a = ; int *pa = (int *)&a; printf("pa指向的地址为:%p a的地址为:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return ; }
我们分别用gcc和g++编译他,然后分别看结果,如下:
惊奇地发现,虽然都能顺利通过编译,但是C的执行和C++的执行竟然不一样!
好吧,下面解释原因。
还是要声明一下,C语言中const就是一个值不能改变的变量,就是个受限制的变量,但是,我们虽然我们不能通过a修改那块内存的值,但是我们可以通过指针间接去修改。这里要注意那个强制类型转换,如果不写强制类型转换,编译器就会报错,是允许将const int *赋值给int*的。在C++中,这一点和C是一样的,就是虽然我们不能通过a本身修改那块内存的值,但是我们可以通过指针间接去修改。但是为什么C和C++中的输出不一样呢?原因就是C++在读a的时候,其实是去代码段中读字面常量10去了,而C是读a所标识的那块栈区的内存。其实a所标识的内存的内容都已经变成11了,无论是C还是C++都是一样,区别就在于C读const数据和读普通变量一样,都是从数据段(如果是局部变量就是从栈区)读取数组,而C++却是读取代码段的字面常量!(间接修改const的时候,当然都是修改的数据区或栈区的内存,而不是代码段,因为代码段是只读的)
所以C++中const修饰的可以认为就是常量!但是C语言中却不能这么认为。
最后要小心C++中的const蜕变成C语言中的const。
其实通过上面的分析,我们应该可以得出一个结论:C++中的const之所以和C语言中的const不一样,C++中的const之所以能够看成常量,就是因为C++在读取const的时候,实际上读取的是代码段的字面常量,而不是数据区(对于全局变量来说是静态区,对于局部变量来说是栈区)的内存中的数值。
那么问题来了:如果const保存的不是一个字面常量呢?
看下面代码:
#include <stdio.h> int main() { int i = ; const int a = i; int *pa = (int *)&a; printf("pa指向的地址为:%p a的地址为:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return ; }
几乎还是同样的代码,只是先把字面常量10赋值给了变量i,然后用i初始化const int a,但是我们发现,在C++中,执行结果却变了。
为什么呢?前面强调了,C++读取const的值,实际上是读取代码段的字面常量,那么,如果我们初始化const的时候,给它的不是字面量(或者是常量表达式),那么他就没有字面量可以读啦!这时候就只能退而求其次,去读数据区内存中的值啦!这个时候,C++中的const和C语言中的const就一样了。
需要注意的是 sizeof是C和C++中的运算符,而且他的值通常都是在编译时确定的,可以认为是一个字面常量。
比如:
#include <stdio.h> int main() { const int a = sizeof(int); int *pa = (int *)&a; printf("pa指向的地址为:%p a的地址为:%p\n",pa,&a); (*pa)++; printf("a = %d,*pa = %d\n",a,*pa); return ; }
此外在类中使用const修饰类成员变量的时候也要小心,因为也会退化成C语言中的const
比如:
#include <stdio.h> class A{ public: const int a; int array[a]; A(int i):a(i) { } }; int main() { return ; }
编译器会报错:
首先,类只是定义类型的地方,不能再类中初始化成员变量,所以在类定义中写const int a = 10是不对的,const成员的初始化要放在初始化列表中。我们知道,在对象创建之前才会调用构造函数进行对象的初始化,所以在编译时期我们根本就不知道a的值。所以把a当做数组的长度是有问题的。
我们可以这样:
#include <stdio.h> class A{ public: static const int a = ; int array[a]; A() { } }; int main() { return ; }
这样定义的a就可以当成一个常量来使用了。注意的是,这时候,a的语句不是声明了,而是定义+初始化,而且C++只允许这种情况可以在类内初始化,就是当变量被 static 和const修饰,且为int类型时。(当然这种情况依然可以在类外定义和初始化,只不过后面就不能用a定义数组的长度了,也会提示数组的长度不确定)
简单总结一下就是:
C语言中const就是一个受限制的变量,读取const时是从数据区的内存读取的(全局从静态去,局部从栈区),可以用指针间接修改其标识的数据区的内存区域,在读取const的值时,也是读取它标识的数据区的内存中的值。
在C++中,const大多数情况下可以当成常量来使用,这是因为,虽然C++也会为const在数据区开辟内存(C++尽量不这样左),我们也可以通过指针(或者非常量的引用)来简介修改其标识的数据区的内存区域的值,但是,在读取const时,不是读取数据区的内存区域中的值(也很有可能根本就没有分配内存),而是读取代码段的字面常量。所以可以达到常量的效果。
最后要警惕C++中const退化成C语言中的const,有两种情况,一种是初始化的const的时候是用变量初始化的,而不是字面常量(或常量表达式)。第二种情况就是const修饰类中成员变量的时候。
给一个建议:如果我们想用const定义常量,那么我们就要用字面常量或常量表达式初始化const,而且要将const用static修饰(注意在C++中如果定义的是全局的const,默认为static修饰,但是在类中并不是这样的,这也是为什么我们在类中定义常量要用static修饰)。在类中定义常量的时候要同时用cosnt和static修饰,而且尽量在类的内部进行初始化。
如果你觉得对你有用,请赞一个吧
C-C++到底支不支持VLA以及两种语言中const的区别的更多相关文章
- Safari支不支持HTML5录音? 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API。
先放结论:Safari支不支持HTML5录音? ——据我调查,不支持. 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API. 可是当使用Can I us ...
- 判断电脑CPU硬件支不支持64位
你可以在注册表中查看: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment\PROCESSO ...
- 分享两种实现Winform程序的多语言支持的解决方案
因公司业务需要,需要将原有的ERP系统加上支持繁体语言,但不能改变原有的编码方式,即:普通程序员感受不到编码有什么不同.经过我与几个同事的多番沟通,确定了以下两种方案: 方案一:在窗体基类中每次加载并 ...
- Windows2003 IIS6.0支持32位和64位两种模式的设置方法
IIS 6.0 可支持 32 位和 64 位两种模式.但是,IIS 6.0 不支持在 64 位版本的 Windows 上同时运行这两种模式.ASP.NET 1.1 只在 32 位模式下运行.而 ASP ...
- WPF工作笔记:本地化支持、主进程通知、两种最常用异步编程方式
1.本地化支持 (1)重写控件默认的依赖属性LanguageProperty FrameworkElement.LanguageProperty.OverrideMetadata( typeof(Fr ...
- 自制Javascript分页插件,支持AJAX加载和URL带参跳转两种初始化方式,可用于同一页面的多个分页和不同页面的调用
闲话部分 最近闲着实在无聊,就做了点小东西练练手,由于原来一直在用AspNetPager进行分页,而且也进行了深度的定制与原有系统整合的也不错,不过毕竟是用别人的,想着看自己能试着做出来不能,后台的分 ...
- Javscript轮播 支持平滑和渐隐两种效果(可以只有两张图)
原文:Javscript轮播 支持平滑和渐隐两种效果(可以只有两张图) 先上两种轮播效果:渐隐和移动 效果一:渐隐 1 2 3 4 效果二:移动 1 2 3 4 接下来,我们来大致说下整个轮播的思 ...
- Javascript轮播 支持平滑和渐隐两种效果
Javascript轮播 支持平滑和渐隐两种效果 先上两种轮播效果:渐隐和移动 效果一:渐隐 1 2 3 4 效果二:移动 1 2 3 4 接下来,我们来大致说下整个轮播的思路: 一.先来看简单的 ...
- 异步编程的两种模型,闭包回调,和Lua的coroutine,到底哪一种消耗更大
今天和人讨论了一下CPS变形为闭包回调(典型为C#和JS),以及Lua这种具有真正堆栈,可以yield和resume的coroutine,两种以同步的形式写异步处理逻辑的解决方案的优缺点.之后生出疑问 ...
随机推荐
- swipe和swiper的区别
swipe.js--移动WEB页面内容触摸滑动类库 参考http://www.jiawin.com/swipe-mobile-touch-slider 1.swipe只提供简单轮播切换,底部的圆点颜色 ...
- ASP.NET-页面间的数据传递
暑假期间做项目时遇到相关问题,总结如下,与大家分享 1.通过查询字符串传递 这种方式是将参数附加在网址的后面,传递数据简单,但容易暴露,一般用于传递一些简单的数据. 例如,在Default1.aspx ...
- NYOJ--187--快速查找素数(筛选法,素数打表)
快速查找素数 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 现在给你一个正整数N,要你快速的找出在2.....N这些数里面所有的素数. 输入 给出一个正整数数N ...
- spring学习之spring 插件 for eclipse
1) 在公司一直使用固定的eclipse IDE版本3.3 确实太out了. eclipse官方网址:http://download.eclipse.org 奇怪的是eclipse 发布的版本顺序是 ...
- 安徽省2016“京胜杯”程序设计大赛_E_转啊转
转啊转 Time Limit: 1000 MS Memory Limit: 65536 KB Total Submissions: 59 Accepted: 15 Description 在二 ...
- Swift学习之方法定义参数有默认值的时候
func testParms(first fir:String, options opt:JSONSerialization.ReadingOptions = []) -> Bool { ret ...
- 【有意思的BUG】后端多处数据校验 前端数据校验
软件(尚处在开发阶段的软件)会犯许多低级的错误,这些错误以你在生活中的经验而言简直莫名其妙.往往你认为这个小功能怎么可能有BUG呢,是的,你猜对了!! 拿1个简单的结构举例:后端页面[1]+后端页面[ ...
- 重定向URL
重定向的原因有哪些?[1]网址变更了[2]权限不够 [1]网址变更了 [举例]以google为例,之前我们可以访问www.google.cn,但是后来Z.F不让我们访问它了,那么google公司为了避 ...
- Fortran调用C语言小计
先简单记录下问题,以后再给续解决方案 1,关于fortran中module的使用 2,没有参数可以正常调用,当需要传递参数时,由于两者语言的差异,字符串结束符号不同,应该注意 3,关于调用C函数时,调 ...
- expungeStaleEntries函数解析
1 /** * Reference queue for cleared WeakEntries */ // 所有Entry在构造时都传入该queue private final ReferenceQu ...