C语言中的long long int vs. long int vs. int64_t


87

在使用C ++类型特征时,我经历了一些奇怪的行为,并且将我的问题缩小到这个古怪的小问题,由于我不想留下任何可能引起误解的信息,因此我将作大量解释。

假设您有一个像这样的程序:

#include <iostream>
#include <cstdint>

template <typename T>
bool is_int64() { return false; }

template <>
bool is_int64<int64_t>() { return true; }

int main()
{
 std::cout << "int:\t" << is_int64<int>() << std::endl;
 std::cout << "int64_t:\t" << is_int64<int64_t>() << std::endl;
 std::cout << "long int:\t" << is_int64<long int>() << std::endl;
 std::cout << "long long int:\t" << is_int64<long long int>() << std::endl;

 return 0;
}

在使用GCC(以及32位和64位MSVC)的32位编译中,程序的输出为:

int:           0
int64_t:       1
long int:      0
long long int: 1

但是,由64位GCC编译产生的程序将输出:

int:           0
int64_t:       1
long int:      1
long long int: 0

这很奇怪,因为它long long int是一个带符号的64位整数,并且就所有意图和目的而言,都与long intandint64_t类型相同,因此从逻辑上讲,int64_tlong int并且long long int将是等效类型-使用这些类型时生成的程序集是相同的。一看就stdint.h告诉我为什么:

# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

在64位编译中,int64_tlong int,而不是long long int(显然)。

解决这种情况的方法非常简单:

#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif

但这是骇人听闻的,并且扩展性不好(实体的实际功能uint64_t等)。 所以我的问题是:有没有办法告诉编译器along long int也是aint64_t,就像long intis一样?


我最初的想法是,由于C / C ++类型定义的工作方式,这是不可能的。没有一种方法可以将基本数据类型的类型等效性指定给编译器,因为这是编译器的工作(允许这样做可能会破坏很多事情),并且typedef只有一种方法。

我也不是很想在这里得到答案,因为这是一个超级骗局,我不怀疑任何人都不会在不妥善设计示例的情况下关心它(这是否应该是社区Wiki?) 。


附录:我之所以使用部分模板专业化,而不是像下面这样的简单示例,是因为

void go(int64_t) { }

int main()
{
    long long int x = 2;
    go(x);
    return 0;
}

是因为上述示例仍可以编译,因为它long long int可以隐式转换为int64_t


追加:到目前为止,唯一的答案是假设我想知道类型是否为64位。我不想误导人们以为我对此很在意,并且可能应该提供更多示例说明此问题的所在。

template <typename T>
struct some_type_trait : boost::false_type { };

template <>
struct some_type_trait<int64_t> : boost::true_type { };

在此示例中,some_type_trait<long int>将为boost::true_type,但some_type_trait<long long int>不会。尽管这在C ++的类型概念中是有意义的,但并不理想。

另一个示例使用的是类似限定符same_type(在C ++ 0x Concepts中很常见):

template <typename T>
void same_type(T, T) { }

void foo()
{
    long int x;
    long long int y;
    same_type(x, y);
}

该示例无法编译,因为C ++(正确)看到类型不同。g ++将无法编译,并显示以下错误:没有匹配的函数调用same_type(long int&, long long int&)

我想强调指出,我理解为什么会这样,但是我正在寻找一种解决方法,它不会迫使我在各处重复执行代码。


出于好奇,您的示例程序对sizeof每种类型的结果是否都相同?也许编译器对待大小的long long int区别。
布莱尔·霍洛威

您是否启用了C ++ 0x进行编译?C ++ 03没有<cstdint>,因此也许它不得不说“这是一个扩展”(它是)的事实正在愚弄它。
GManNickG 2010年

是的,我应该已经指定了我正在使用--std=c++0x。是的,sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8
特拉维斯·高克尔

1
尚未有人提及,但万一它被忽略了:long并且long long是不同的类型(即使它们具有相同的大小和表示形式)。 int64_t始终是另一个现有类型的别名(尽管它的名称是,typedef不创建新类型,它只是给已经存在的一种别名)
MM

3
答案/评论中缺少一个重要的说明,当这个古怪的想法打击了我时,这对我有所帮助:切勿使用固定大小的类型来可靠地专门化模板。始终使用基本类型并涵盖所有可能的情况(即使您使用固定大小的类型来实例化这些模板)。所有可能的情况都意味着:如果需要使用实例化int16_t,然后使用short和进行专门化,那么int您将被覆盖。(以及signed char您是否喜欢冒险)
Irfy'2

Answers:


49

您无需转到64位就可以看到类似的内容。考虑int32_t在常见的32位平台上使用。这可能是typedef“编作为int或作为long,但显然只有两个在时间中的一个。int并且long当然是不同的类型。

不难看出int == int32_t == long在32位系统上没有解决方法。出于同样的原因,无法long == int64_t == long long在64位系统上进行制作。

如果可以的话,对于重载的代码,可能产生的后果将非常痛苦foo(int)foo(long)并且foo(long long)-突然之间,对于同一个重载,它们将具有两个定义?

正确的解决方案是您的模板代码通常不应依赖于精确的类型,而应依赖于该类型的属性。same_type对于特定情况,整个逻辑仍然可以:

long foo(long x);
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);

即,foo(int64_t)当与完全相同时,未定义重载foo(long)

[edit]使用C ++ 11,我们现在有了一种标准的编写方式:

long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);

[edit]或C ++ 20

long foo(long x);
int64_t foo(int64_t) requires (!std::is_same_v<int64_t, long>);

1
不幸的消息是,例如在64位MSVC19(2017)上sizeof() long,它int是相同的,但是std::is_same<long, int>::value返回false。在OSX HighSierra上的AppleClang 9.1上也是如此。
Ax3l

3
@ Ax3l:这并不奇怪。从ISO C 90开始,几乎每个编译器都至少有一对这样的编译器。
MSalters

是的,它们是不同的类型。
Ax3l

6

您是否想知道某个类型是否与int64_t相同,还是想知道某些东西是否为64位?根据您提出的解决方案,我认为您正在询问后者。在这种情况下,我会做类似的事情

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64

1
您不是想念areturn和分号吗?
卡萨布兰卡2010年

1
不过,您应该sizeof为此使用。
Ben Voigt 2010年

5
无论long long int和long int大小是否相同,它们的类型都不相同。该行为不是错误的。那只是C ++。
Logan Capaldo 2010年

5
这不是名义类型的限制。这是毫无意义的名义打字的限制。在过去,事实上的标准是short= 16位,long= 32位和int=本机大小。在这些日子里的64位,int并且long意味着什么了。
dan04

1
@ dan04:它们比以往任何时候都有意义。 short至少16位,int至少是16位,并且long是至少32位,以(稀松符号如下)短<= INT <=长。您提到的“过去”从未存在;在语言所施加的限制范围内,总会有变化。“所有的世界是一个x86”的谬论是一样危险的,因为旧的“整个世界是一个VAX谬论。
基思·汤普森

1

所以我的问题是:有没有办法告诉编译器,long long int也是int64_t,就像long int一样?

这是一个很好的问题,但我怀疑答案是否定的。

另外,along int可能不是long long int


# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

我相信这是libc。我怀疑你想更深入。

在使用GCC(以及32位和64位MSVC)的32位编译中,程序的输出为:

int:           0
int64_t:       1
long int:      0
long long int: 1

32位Linux使用ILP32数据模型。整数,long和指针均为32位。64位类型是long long

Microsoft在“数据类型范围”中记录了范围。的说long long等于__int64

但是,由64位GCC编译产生的程序将输出:

int:           0
int64_t:       1
long int:      1
long long int: 0

64位Linux使用LP64数据模型。long是64位和long long64位。与32位一样,Microsoft在数据类型范围中记录范围,并且long long仍然是__int64

有一个ILP64数据模型,其中所有数据都是64位的。您必须做一些额外的工作才能获得word32类型的定义。另请参阅类似64位编程模型的文章:为什么选择LP64?


但这真是骇人听闻,无法很好地扩展(实质功能,uint64_t等的实际功能)...

是的,它变得更好。GCC混合并匹配应该采用64位类型的声明,因此即使您遵循特定的数据模型,也很容易遇到麻烦。例如,以下内容导致编译错误并告诉您使用-fpermissive

#if __LP64__
typedef unsigned long word64;
#else
typedef unsigned long long word64;
#endif

// intel definition of rdrand64_step (http://software.intel.com/en-us/node/523864)
// extern int _rdrand64_step(unsigned __int64 *random_val);

// Try it:
word64 val;
int res = rdrand64_step(&val);

结果是:

error: invalid conversion from `word64* {aka long unsigned int*}' to `long long unsigned int*'

因此,忽略LP64并将其更改为:

typedef unsigned long long word64;

然后,进入定义LP64和使用NEON的64位ARM IoT小工具:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.