c++在多大程度上是一种静态类型语言?

To what extent is C++ a statically-typed language?

本文关键字:一种 静态类 型语言 程度 c++      更新时间:2023-10-16

我曾经认为这个问题的答案是"100%",但最近有人指出一个例子,让我值得三思。考虑将C数组声明为具有自动存储持续时间的对象:

int main()
{
    int foo[42] = { 0 };
}

这里,foo的类型显然是int[42]。相反,考虑这种情况:

int main()
{
    int* foo = new int[rand() % 42];
    delete[] foo;
}

这里,foo的类型是int*,但是如何在编译时告诉new表达式创建的对象的类型?(强调是为了强调我不是在谈论由new表达式返回的指针,而是关于由new表达式创建的数组对象)。

这是c++ 11标准第5.3.4/1段对new表达式结果的规定:

[…由new-expression创建的实体具有动态存储时间(3.7.4)。[注:这样的人的一生。实体并不一定局限于创建它时的范围。-end note]如果实体不是数组对象时,new-expression返回一个指向所创建对象的指针。如果是数组,new-expression返回指向数组初始元素的指针。

我曾经认为,在c++中,所有对象的类型都是在编译时确定的,但上面的例子似乎推翻了这种观点。此外,根据第1.8/1段:

[…对象的属性是在时确定的创建。一个对象可以有一个名称(第3条)。一个对象有一个存储时间(3.7),这影响它的寿命(3.8)。对象有一个类型(3.9)。[…]

我的问题是:

  1. 最后一段引用的"properties"是什么意思?显然,一个对象的名称不能算作是"当对象被创建时"确定的东西-除非这里的"创建"的含义与我认为的不同;
  2. 还有其他对象的类型只在运行时确定的例子吗?
  3. 在多大程度上说c++是一种静态类型语言是正确的?或者更确切地说,在这方面对c++进行分类的最合适的方法是什么?

如果有人能详细说明以上几点中的至少一条,那就太好了。

编辑:

标准似乎清楚地表明,new表达式确实创建了一个数组对象,而不是像一些人指出的那样,只是将几个对象布置为一个数组。根据第5.3.4/5段(由Xeo提供):

当分配的对象是数组时(即,使用noptr-new-declarator语法或new-type-id或)type-id表示数组类型),new表达式产生指向数组初始元素(如果有的话)的指针。[注:new intnew int[10]的类型都是int*, new int[i][10]的类型是int (*)[10]noptr-new-declarator中的属性指定符-seq指定为关联的数组类型

new-expression不创建具有运行时变化数组类型的对象。它创建了许多对象,每个对象都是静态类型int。这些对象的数量是静态未知的。


c++为动态类型提供了两种情况(第5.2.8节):

  • 与表达式
  • 的静态类型相同
  • 当静态类型为多态时,派生最多的对象的运行时类型

这两个都没有给new int[N]创建的对象一个动态数组类型。


从学究底上讲,new-expression的求值会创建无限数量的重叠数组对象。从3.8 p2:

[注:数组对象的生命周期从获得合适大小和对齐的存储空间开始,到该数组占用的存储空间被重用或释放时结束。12.6.2描述了基对象和成员子对象的生命周期。

所以如果你想谈论由new int[5]创建的"数组对象",你不仅要给它类型int[5],还要给它类型int[4], int[1], char[5*sizeof(int)]struct s { int x; }[5]

我认为这相当于说数组类型在运行时不存在。对象的类型应该是限制性信息,并告诉您有关其属性的一些信息。允许将内存区域视为无限数量的具有不同类型的重叠数组对象,实际上意味着该数组对象是完全无类型的。运行时类型的概念只对存储在数组中的元素对象有意义。

术语"静态类型"answers"动态类型"适用于表达式。

静态类型

在不考虑执行语义的情况下对程序进行分析而得到的表达式(3.9)的

类型


动态类型

& lt; glvalue>由glvalue表达式表示的glvalue所指向的最派生对象的类型(1.8)

此外,您可以看到,动态类型与静态类型只有在静态类型可以派生时才不同,这意味着动态数组类型始终与表达式的静态类型相同。

你的问题是:

但是如何在编译时判断new表达式创建的对象的类型呢?

对象有类型,但它们不是没有指向对象的表达式的"静态"或"动态"类型。给定一个表达式,静态类型在编译时总是已知的。如果没有派生,则动态类型与静态类型相同。

但是你问的是独立于表达式的对象类型。在你给出的例子中,你要求创建一个对象,但你没有指定你想在编译时创建的对象的类型。你可以这样看:

template<typename T>
T *create_array(size_t s) {
    switch(s) {
        case 1: return &(*new std::array<T, 1>)[0];
        case 2: return &(*new std::array<T, 2>)[0];
        // ...
    }
}

这没什么特别或独特的。另一种可能是:

struct B { virtual ~B() {}};
struct D : B {};
struct E : B {};
B *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return new D;
    }
    return new E;
}

或:

void *create() {
    if (std::bernoulli_distribution(0.5)(std::default_random_engine())) {
        return reinterpret_cast<void*>(new int);
    }
    return reinterpret_cast<void*>(new float);
}

new int[]的唯一区别是,您无法看到它的实现,看到它在不同类型的对象之间进行选择来创建。

我过去认为,在c++中,所有对象的类型都是在编译时确定的,但上面的例子似乎推翻了这种看法。

你举的例子是关于物品的储存时间。c++可以识别三种存储持续时间:

  1. 静态存储时间是全局静态变量和局部静态变量的持续时间。
  2. 自动存储持续时间是"堆栈分配"函数局部变量的持续时间。
  3. 动态存储时长指newmalloc等动态分配内存的时长。

这里"dynamic"一词的使用与对象的类型无关。它指的是实现必须如何存储组成对象的数据。

我过去认为,在c++中,所有对象的类型都是在编译时确定的,但上面的例子似乎推翻了这种看法。

在您的示例中,有一个类型为int*的变量。底层数组没有一个实际的数组类型,它可以以任何有意义的方式恢复到程序中。这里没有动态输入