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的区别的更多相关文章

  1. Safari支不支持HTML5录音? 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API。

    先放结论:Safari支不支持HTML5录音? ——据我调查,不支持. 现在浏览器中最好的解决方案是WebRTC下的 navigator.getUserMedia API. 可是当使用Can I us ...

  2. 判断电脑CPU硬件支不支持64位

    你可以在注册表中查看: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager\Environment\PROCESSO ...

  3. 分享两种实现Winform程序的多语言支持的解决方案

    因公司业务需要,需要将原有的ERP系统加上支持繁体语言,但不能改变原有的编码方式,即:普通程序员感受不到编码有什么不同.经过我与几个同事的多番沟通,确定了以下两种方案: 方案一:在窗体基类中每次加载并 ...

  4. Windows2003 IIS6.0支持32位和64位两种模式的设置方法

    IIS 6.0 可支持 32 位和 64 位两种模式.但是,IIS 6.0 不支持在 64 位版本的 Windows 上同时运行这两种模式.ASP.NET 1.1 只在 32 位模式下运行.而 ASP ...

  5. WPF工作笔记:本地化支持、主进程通知、两种最常用异步编程方式

    1.本地化支持 (1)重写控件默认的依赖属性LanguageProperty FrameworkElement.LanguageProperty.OverrideMetadata( typeof(Fr ...

  6. 自制Javascript分页插件,支持AJAX加载和URL带参跳转两种初始化方式,可用于同一页面的多个分页和不同页面的调用

    闲话部分 最近闲着实在无聊,就做了点小东西练练手,由于原来一直在用AspNetPager进行分页,而且也进行了深度的定制与原有系统整合的也不错,不过毕竟是用别人的,想着看自己能试着做出来不能,后台的分 ...

  7. Javscript轮播 支持平滑和渐隐两种效果(可以只有两张图)

    原文:Javscript轮播 支持平滑和渐隐两种效果(可以只有两张图) 先上两种轮播效果:渐隐和移动   效果一:渐隐 1 2 3 4 效果二:移动 1 2 3 4 接下来,我们来大致说下整个轮播的思 ...

  8. Javascript轮播 支持平滑和渐隐两种效果

    Javascript轮播 支持平滑和渐隐两种效果 先上两种轮播效果:渐隐和移动   效果一:渐隐 1 2 3 4 效果二:移动 1 2 3 4 接下来,我们来大致说下整个轮播的思路: 一.先来看简单的 ...

  9. 异步编程的两种模型,闭包回调,和Lua的coroutine,到底哪一种消耗更大

    今天和人讨论了一下CPS变形为闭包回调(典型为C#和JS),以及Lua这种具有真正堆栈,可以yield和resume的coroutine,两种以同步的形式写异步处理逻辑的解决方案的优缺点.之后生出疑问 ...

随机推荐

  1. 47. leetcode 437. Path Sum III

    437. Path Sum III You are given a binary tree in which each node contains an integer value. Find the ...

  2. 4. leetcode 461. Hamming Distance

    The Hamming distance between two integers is the number of positions at which the corresponding bits ...

  3. [转载]无旋treap:从好奇到入门(例题:bzoj3224 普通平衡树)

    转载自ZZH大佬,原文:http://www.cnblogs.com/LadyLex/p/7182491.html 今天我们来学习一种新的数据结构:无旋treap.它和splay一样支持区间操作,和t ...

  4. 【HOSTS相关】什么时候使用127.0.0.1

    什么时候使用127.0.0.1 在测试环境,如果想知道上线后服务发生异常中断的情况下界面会如何展示,这个时候有2种方式: 第1种是:由开发人员协助,比如后台开发人员在服务器上使这个服务停止,或者由前端 ...

  5. 移动端为何不使用click而模拟tap事件及解决方案

    移动端click会遇到2个问题,click会有200-300ms的延迟,同时click事件的延迟响应,会出现穿透,即点击会触发非当前层的点击事件. 为什么会存在延迟? Google开发者文档中有提到: ...

  6. Markdown学习笔记(一) 基本的Markdown标签

    Markdown是一种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以使普通文本内容具有一定的格式. Markdown的语法简洁明了.学习容易,而且功能比纯文本更强,因此有很多人用它写 ...

  7. SQL连接、合并、子查询

    连接:连接分为内连接.外连接.交叉连接 内连接和外连接都是在笛卡尔积的基础做一些修改. 合并查询:把两个相似的结果可以用union联合起来. mysql> select id,time from ...

  8. Weex入门指南

    背景 由于公司项目需要,需求变化频繁,计划总改不上变化,由于app更新版本周期长,不能很好应对这种变化,正在此前提下热修复和热更新技术也有了发展的空间,不管热修复还是热更新,都是对app内容或者逻辑的 ...

  9. [attribute|=value]和[attribute^=value]选择器区别

    1.[attribute|=value] [attribute|=value]选择器说明:http://www.w3school.com.cn/cssref/selector_attribute_va ...

  10. 从零宽断言说起到用python匹配html标签内容

    版权声明:本文为博主原创文章,转载请附带原文网址http://www.cnblogs.com/wbchanblog/p/7411750.html ,谢谢! 提示:本文主要是讲解零宽断言,所以阅读本文需 ...