【问题标题】:Does `sizeof` *really* evaluate to a `std::size_t`? Can it?`sizeof` *真的*评估为`std :: size_t`吗?它可以?
【发布时间】:2014-01-15 12:43:43
【问题描述】:

阅读以下标准段落:

[C++11: 5.3.3/6]:sizeofsizeof... 的结果是std::size_t 类型的常量。 [注意:std::size_t 在标准头文件<cstddef> (18.2) 中定义。 ——尾注]

现在:

[C++11: 18.2/6]: size_t 类型是实现定义的无符号整数类型,其大小足以容纳任何对象的字节大小。

当然,该段落并不要求 size_t 是使用 typedef 定义的类型别名,但由于它明确声明可由标准标头 <cstddef> 提供,我认为我们可以将其视为未能包含<cstddef> 应取消任何保证size_t 可用于程序。

但是,根据第一个引用,我们无论如何都可以获得std::size_t 类型的表达式。

We can actually demonstrate both of these facts:

int main()
{
    typedef decltype(sizeof(0)) my_size_t;

    my_size_t x   = 0;  // OK
    std::size_t y = 1;  // error: 'size_t' is not a member of 'std'
}

std::size_t 对程序不可见,但sizeof(0) 仍然给我们一个?真的吗?

因此说5.3.3/6有缺陷的 是不正确的,并且它实际上具有“与std::size_t 解析为相同的类型”,但不是 std::size_t 自己?

当然,如果std::size_t 是类型别名,则两者是相同的,但同样,这实际上是不需要的。

【问题讨论】:

  • 我很确定“与 std::size_t 解析为相同的类型”是std::size_t,无论size_t 是如何定义的或者它是否是任何东西的别名。也就是说,我还不足以作为 C++ 语言律师来回答这个问题。
  • “我认为我们可以理解为未能包含 应该消除任何 size_t 可用于程序的保证” - 您需要包含 cstddef 才能使用 size_t名称,但不是 size_t 类型。
  • @user2357112: intlong 是不同的类型,尽管在我的平台上具有相同的属性。
  • @LightnessRacesinOrbit 相关:operator newoperator delete 及其变体也可以在不包含任何头文件的情况下使用,但在标准中的某处(我想是 3.7.4 但不确定),我读到使用它们“不会使操作员std::operator new(std::size_t) 可见”,或类似的东西。
  • @H2CO3 该类型在语言中可用,不需要库。该库为其提供了一个方便的名称,仅此而已。

标签: c++ c++11 std language-lawyer


【解决方案1】:

标准只是要求sizeof(expr) 的类型与std::size_t 的类型相同。没有强制要求使用 sizeof(expr) 使名称 std::size_t 可用,因为 std::size_t 只是命名了内置整数类型之一,所以没有真正的问题。

【讨论】:

  • @LightnessRacesinOrbit:类型类型名称是有区别的。类型始终存在,但 name 不存在。
  • @LightnessRacesinOrbit:嗯,这很简单,不是吗?你可以声明names,也可以定义types。一个类型可以有多个名称,用typedefusing 声明,或作为模板参数,但只有一个定义——它是内置的或用户定义的。而且 trait 仅适用于类型,而不适用于名称,因此没有 trait 可以告诉您某事物是 typedef 还是主名称。
  • 无论如何我相信这里的问题在于类型和它们的名字之间的区别。一个类型可能存在并对一个表达式进行分类,即使它是不可表达的(参见 lambdas)。 sizeof(T) 产生与std::size_t 相同的类型这一事实与std::size_t 可能无法说出(如果不包括<cstddef>)这一事实并不冲突。拼写出sizeof(T) 并不假定您可以“说”std::size_t
  • @H2CO3:别忘了std::typeinfo 的情况。还有std::initializer_list...
  • @H2CO3:大约 3 年前,这个想法随着 C 中的可变参数而失败。 WG21 从未假设这两者可以完全分开。
【解决方案2】:

不要将地图与领土混淆。

类型可以通过类型名来命名。这些类型名可以是内置的,也可以是用户定义的类型,甚至可以是template 参数并根据实例化引用多种不同的类型。

但名称不是类型。显然,标准并不要求所有类型都有名称——经典的 struct {} 是一种没有名称的类型。

std::size_t 是一个类型名。它命名sizeof(expression) 返回的类型。

编译器可以有一个规范的类型名称 -- __size_t 是它拥有唯一的内置规范类型名称的一种方式。

该条款中的标准保证无论sizeof(expression) 的类型是什么,一旦你#include <cstddef>,名称std::size_t 现在指的是该类型。

在标准中,它们通过名称引用类型。他们没有说“这个类型名所指的类型”,而只是说“类型 $NAME$”。编译器可以根据需要决定int__int_32_fast 的另一个名称,标准也不会反对。

std::nullptr_tstd::initializer_list<Ts>std::type_info 也会发生同样的情况:使用这些类型的变量并不总是需要在程序中包含为您提供这些类型名称的标头。

传统的 C/C++ 内置类型都具有不需要标头的规范名称。缺点是这会破坏现有代码,因为全局范围内的新类型名会与其他标识符发生冲突。

通过使用“无名类型”,您可以通过包含头文件来获得它们的名称,我们避免了这个问题。

【讨论】:

  • 你觉得 5.7.6 怎么样?对于std::ptrdiff_t,它完全符合我的建议。这至少不是措辞的不一致吗?
  • @LightnessRacesInOrbit 是的,措辞不一致。与nullptr_t 相同——很多不一致的措辞。应该可以清零了吧?但是很深奥,值得努力吗?
  • 不,怀疑。只是希望我们都同意:)
  • 如果我们添加这个,我认为这就是答案
  • 类型和类型名...这让我想起了变量的名称和指针。我们可以通过名称访问变量,也可以通过可以从函数返回的指针访问它(在这种情况下,我们不需要包含声明变量名称的标头)。数据只是内存中的一条记录,可以通过名称或指针访问。类似地,类型是编译器内存中的记录,可以通过类型名或运算符返回的内容(使用 auto 或 decltype)从程序中引用。
【解决方案3】:

据我了解,这个标准段落需要如下表达:

typeid(sizeof(0)) == typeid(std::size_t)

将始终产生true。如果您使用实际标识符 std::size_t::size_t 或任何其他别名/typedef,则只要保留类型的标识(根据 std::typeinfo::operator==()),将是无关紧要的。

同样的类型标识问题出现在该语言的其他地方。例如,在我的 64 位机器中,由于函数重定义,以下代码无法编译:

#include <cstddef>
void foo(std::size_t x)
{}

void foo(unsigned long x)
{}

【讨论】:

  • 所以它应该说“是std::size_t 类型的常量(或由它的其他名称之一给出的相同类型,如果该名称对程序不可见,只是说)”?
  • @LightnessRacesinOrbit:但是所有这些措辞都是不必要的。如果我写typedef int number; number x;,那么x 类型int,它 类型number,因为它们是相同的。两个名字,一种类型。
  • 这让我感到不安,这让我很想像。
  • @LightnessRacesinOrbit:但是,您必须在需要特定类型的任何地方都这样做。我可以写typedef int number; number main() {}吗?如果是这样,3.6.1 是否应该说“它 [main] 应具有 int 类型的返回类型(或由其其他名称之一给出的相同类型”?
  • @LightnessRacesinOrbit:嗯,std::ptrdiff_t 仅在 5.7.6/7 中命名,但std::size_t 遍布整个文档。特别有趣的是 3.7.4 中的注释:“引用这些函数之一的新表达式、删除表达式或函数调用 [operator new/delete] 不包括标题 是格式正确的。但是,引用 std 或 std::size_t 是不正确的,除非已通过包含适当的标头来声明名称".
【解决方案4】:

是的。

sizeof 产生的类型是一些无符号整数类型;实现定义了它是哪一个。

例如,在某些特定实现中,sizeof 表达式的类型可能是 unsigned long

std::size_t,如果是typedef,只不过是unsigned long 的替代名称。所以这两条语句:

sizeof ...的类型是unsigned long类型的常量

sizeof ...的类型是std::size_t类型的常量

对那个实现说的完全一样unsigned long 类型和std::size_t 类型是同一类型。不同之处在于后者对于所有(一致的)实现都是准确的,其中std::size_t 可能是unsigned int 或其他无符号类型的别名。

就编译器而言,sizeof 产生unsigned long 类型的结果;编译器(相对于运行时库)不需要知道名称 size_t

这一切都假设std::size_t(或者只是size_t,如果你在谈论C)是一个typedef。这在 C 或 C++ 标准中都没有说明。尽管如此,通过将size_t 设为typedef,实现可以直接符合标准的要求。我不相信有任何其他便携方式可以满足这些要求。 (它不能是宏或实现定义的关键字,因为这会侵犯用户的名称空间,并且宏不会在 std 命名空间内限定范围。)编译器可以使size_t 除了typedef 之外的一些特定于实现的构造,但是由于typedef 工作得很好,这样做没有意义。恕我直言,如果标准规定 size_t 是 typedef,那就太好了。

(一个无关紧要的问题:真正的问题是标准将结果称为“常量”。在 ISO C 中,“常量”是一个标记,例如整数文字。据我所知,C++ , 没有定义名词“常量”,但它确实引用了 ISO C 对该术语的定义。sizeof ... 是一个常量表达式;它不是一个常量 . 将结果称为“恒定值”是合理的。)

【讨论】:

  • 这一切似乎都依赖于 std::size_t 作为 typedef。
  • @LightnessRacesinOrbit:还能是什么?
  • 我想没什么。还是希望把它写出来。
  • @LightnessRacesinOrbit:同意。我已经更新了我的答案。
【解决方案5】:

它是相同的类型,但您必须包含该标头才能使用它。

【讨论】:

  • 我只是在变量x中使用了它,没有任何麻烦,没有包含header。
猜你喜欢
  • 1970-01-01
  • 2023-04-04
  • 2012-09-11
  • 1970-01-01
  • 2013-10-20
  • 2013-09-19
  • 2021-12-06
  • 2018-07-01
  • 2011-10-25
相关资源
最近更新 更多