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 = 10;

int array[a];

int main()

{

         return 0;

}

 

 

用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 = 10;

const int *p = &a;

int array[a];

 

int main()

{

         return 0;

}

 

注意 const int *p = &a;这句,对a取地址操作,我们知道位于代码段的数据是不取地址的,所以这个时候,只能给a在数据区分配空间了。

于是就出现了新的问题,既然给a分配了数据区的空间,那是不是编译时期就不知道a的值了,因为毕竟编译时期是不能读取数据区的内存的,那么后面数组的定义也就不行了吧?但是答案却相反,依然可以,这是因为当编译器读a的值的时候,不是从数据区的内存中,而是程序段的符号表中读取的那个字面常量10。所以在编译实际依然能够确定数组的长度。

下面的例子应该更能说明这个问题:

 

#include <stdio.h>

int main()

{

         const int a = 10;

         int *pa = (int *)&a;

         printf("pa指向的地址为:%p  a的地址为:%p\n",pa,&a);

         (*pa)++;

         printf("a = %d,*pa = %d\n",a,*pa);

 

         return 0;

}

 

我们分别用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 = 10;

         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 0;

}

 

几乎还是同样的代码,只是先把字面常量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 0;

}

 

 

 

此外在类中使用const修饰类成员变量的时候也要小心,因为也会退化成C语言中的const

比如:

 

#include <stdio.h>

class A{

         public:

                  const int a;

                  int array[a];

                 

                  A(int i):a(i)

                  {

 

                  }

};

int main()

{

         return 0;

}

 

编译器会报错:

 

 

首先,类只是定义类型的地方,不能再类中初始化成员变量,所以在类定义中写const int a = 10是不对的,const成员的初始化要放在初始化列表中。我们知道,在对象创建之前才会调用构造函数进行对象的初始化,所以在编译时期我们根本就不知道a的值。所以把a当做数组的长度是有问题的。

我们可以这样:

 

#include <stdio.h>

class A{

         public:

                  static const int a = 10;

                  int array[a];

                 

                  A()

                  {

 

                  }

};

int main()

{

         return 0;

}

 

这样定义的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修饰,而且尽量在类的内部进行初始化。

 

如果你觉得对你有用,请赞一个吧

 

posted @ 2017-09-14 14:36 青儿哥哥 阅读(...) 评论(...) 编辑 收藏