积分真常数的类型是什么?

时间:2015-08-22 19:37:27

标签: delphi

我很抱歉提出一个非常基本的问题。请考虑以下示例:

Router.map(function() {
  this.route('home', {path : '/'});
  this.resource('posts', function() {
    this.route('new', {path: '/:post_id'});
 });
});

阅读此documentation后,我可以得出结论,负值被解释为 signed ,而正值被解释为 unsigned 。但是,例如 123456 (根据文档将被解释为const c1 = 1; // Is this Byte or ShortInt? c2 = 1234; // Is this Word or Smallint? c3 = 123456; // Is this Cardinal or Integer? )也可以在Cardinal的上下文中使用,我的意思是它用于Integer在计算中使用常量的变量。因此,常量保证始终为Integer,以便对Cardinal进行必要的转换?

2 个答案:

答案 0 :(得分:7)

documentation(XE8是我写的最新版本)告诉你 true constants 有一个类型。但是,在指定实际类型时,文档会产生误导。当我说出误导时,我有些善意。

如果您阅读此官方documentation,那么您会倾向于认为无符号类型比签名类型更受欢迎。但是这个程序表明情况并非如此:

program SO32160057_overloads;

{$APPTYPE CONSOLE}

procedure foo(value: UInt8); overload;
begin
  Writeln('UInt8');
end;

procedure foo(value: UInt16); overload;
begin
  Writeln('UInt16');
end;

procedure foo(value: UInt32); overload;
begin
  Writeln('UInt32');
end;

procedure foo(value: UInt64); overload;
begin
  Writeln('UInt64');
end;

procedure foo(value: Int8); overload;
begin
  Writeln('Int8');
end;

procedure foo(value: Int16); overload;
begin
  Writeln('Int16');
end;

procedure foo(value: Int32); overload;
begin
  Writeln('Int32');
end;

procedure foo(value: Int64); overload;
begin
  Writeln('Int64');
end;

const
  ZeroInt32 = Int32(0);
  ZeroUInt16 = UInt16(0);

begin
  foo(127);
  foo(128);

  foo(32767);
  foo(32768);

  foo(2147483647);
  foo(2147483648);

  foo(9223372036854775807);
  foo(9223372036854775808);

  foo(ZeroInt32);
  foo(ZeroUInt16);
  foo(UInt8(0));
end.

输出结果为:

Int8
UInt8
Int16
UInt16
Int32
UInt32
Int64
UInt64
Int32
UInt16
UInt8

让我们看看另一个程序:

program SO32160057_comparisons;

var
  Int8var:  Int8  = 0;
  Int16var: Int16 = 0;
  Int32var: Int32 = 0;

begin
  if Int8var  <              127  then ;
  if Int8var  <              128  then ;  // line 10
  if Int8var  <        Int16(128) then ;  // line 11
  if Int16var <            32767  then ;
  if Int16var <            32768  then ;  // line 13
  if Int16var <      Int32(32768) then ;  // line 14
  if Int32var <       2147483647  then ;
  if Int32var <       2147483648  then ;  // line 16
  if Int32var < Int64(2147483648) then ;
end.

编译器发出以下警告:

(10): W1022 Comparison always evaluates to True
(10): W1023 Comparing signed and unsigned types - widened both operands
(11): W1022 Comparison always evaluates to True
(13): W1022 Comparison always evaluates to True
(13): W1023 Comparing signed and unsigned types - widened both operands
(14): W1022 Comparison always evaluates to True
(16): W1022 Comparison always evaluates to True
(16): W1023 Comparing signed and unsigned types - widened both operands

因此,通过我的实证分析,编译器会查看整数文字的值,并通过查找以下列表中可以表示值的第一个类型来确定其类型:

  • Int8
  • UInt8
  • Int16
  • UInt16
  • Int32
  • UInt32
  • Int64
  • UInt64

可以使用类型转换语法指定类型来覆盖此规则。例如,要声明值为Int32的{​​{1}},您需要编写0

现在让我们将该规则应用于您在问题中提供的具体示例,即Int32(0)。根据上面的规则,列表中可以表示此值的第一个类型是123456。也称为Int32

现在,因为这是一个无符号类型,您可能希望与无符号Integer变量进行比较将导致警告W1023,比较有符号和无符号类型。但事实并非如此。编译器认识到UInt32是一个正值,我们正在比较两个正值。另一方面,警告以123456发出。

-123456

编译器发出以下警告:

(8): W1022 Comparison always evaluates to True
(8): W1023 Comparing signed and unsigned types - widened both operands

答案 1 :(得分:5)

回答您的原始问题:

  

每个常量的数据类型是什么?

它们或多或少是无类型的,并且在使用它们时会采用类型,就像文字值一样。它们不占用空间并且通常没有地址,除非它们必须(例如字符串,集合等)。 ISTM你可以考虑它们&#34;表示文字的符号&#34;。可以将它们与C(IOW,文本替换)中的简单#define或汇编程序中的立即值进行比较。因此'C'可以是AnsiCharWideCharAnsiStringWideStringUnicodeString,甚至可以分配给PChar PWideCharPAnsiCharExtended。类型取决于你分配的内容,就像文字一样。整数值或浮点类型,集类型等相同。

请注意,编译器(以及 - IMO不正确 - 文档)会在您询问时为您提供一个类型,通常这是可以容纳它的最小整数类型,或Double或{{1}用于浮点值,或者它所属的枚举或集合类型。但那就是AFAICT,只有你问。

只有&#34;无类型&#34;或者常量表达式中可以使用真常量。正如你所说,类型常量(占用真实空间的IOW常量)或多或少类似于不可变变量&#34; (是的,我知道这是一个矛盾的说法),IOW,他们有一个地址,一个类型,一个定义的大小,但它们不是,呃,变量或者是可变的(正如大卫所说,忘了可写类型常量)。

我确信这里的一些人会完全不同意,但是,嘿,这就是我一直认为真正的常数,而这种观点从来没有让我失望过。

  

如何强制常量具有特定数据类型?我的意思是除了类型常量之外的另一种方式,如果我正确的话,它实际上是常量变量。

你可以通过强制转换给出一个真正的常量:

const
  Bla = UInt64(1234); // $0000000000001234
  Doo = Cardinal(-1); // $FFFFFFFF
  Duh = Shortint(rfReplaceAll);
但是,我不认为你可以转换为其中一种浮点类型。这种演员阵容通常是禁止的,也适用于常数(AFAIK,现在不能进行测试)。

现在期待一些尖锐的评论或支持。 : - )

更新

  

因此,常量保证始终为Cardinal,以便对Integer进行必要的转换吗?

不需要施法。无论如何,类型并不总是Cardinal,即使文档这么说(正如我所说,我认为它们是错误的)。是就好像您在该位置使用了字面值123456。你可以说123456也有一种类型,但实际上,假装它并不容易。类型取决于上下文,它可能会编译为类似

        MOV     EAX,123456
        MOV     [TheVariable],EAX

如何解释它取决于变量的类型。例如,如果你这样做:

MyDouble := 123456;

这并不意味着编译Cardinal并且必须转换为Double。它直接编译为Double123456.0。可能存在转换,但仅限于编译器内部。

所以,IMO,不要再担心这种常数的类型了。简单地将它们视为表示文字的符号,并假设它们将获得您期望它们获得的类型。你很少会错。

证明

看看以下代码:

const
  CHi = 'Hello';
  CInt = $1234;
  CTInt: Word = $1234;

var
  CVInt: Word = $1234;

procedure Test;
var
  A: AnsiString;
  U: UnicodeString;
  I: Integer;
  D, E, F: Double;
begin
  A := CHi;
  U := CHi;
  I := CInt;
  D := CInt;
  E := CTInt;
  F := CVInt;
  Writeln(A, U, I, D); // Just to make this compile.
end;

以及对此的反汇编:

Project44.dpr.25: A := CHi;
00419584 8D45FC           lea eax,[ebp-$04]
00419587 BA40964100       mov edx,$00419640
0041958C E84BE0FEFF       call @LStrLAsg
Project44.dpr.26: U := CHi;
00419591 8D45F8           lea eax,[ebp-$08]
00419594 BA54964100       mov edx,$00419654
00419599 E8A2DFFEFF       call @UStrLAsg
Project44.dpr.27: I := CInt;
0041959E C745F434120000   mov [ebp-$0c],$00001234
Project44.dpr.28: D := CInt;
004195A5 33C0             xor eax,eax
004195A7 8945E8           mov [ebp-$18],eax
004195AA C745EC0034B240   mov [ebp-$14],$40b23400
Project44.dpr.29: E := CTInt;
004195B1 0FB705A0D54100   movzx eax,[$0041d5a0]
004195B8 8945D4           mov [ebp-$2c],eax
004195BB DB45D4           fild dword ptr [ebp-$2c]
004195BE DD5DE0           fstp qword ptr [ebp-$20]
004195C1 9B               wait
Project44.dpr.30: F := CVInt;
004195C2 0FB705A2D54100   movzx eax,[$0041d5a2]
004195C9 8945D4           mov [ebp-$2c],eax
004195CC DB45D4           fild dword ptr [ebp-$2c]
004195CF DD5DD8           fstp qword ptr [ebp-$28]
004195D2 9B               wait

如您所见,当分配给AnsiStringUnicodeString时,字符串常量的值不同。复制反汇编的数据部分并不容易,因此您必须在自己的Delphi IDE中进行检查,但在地址$0041961C处,有一个文字AnsiString(refcount -1)&#39;您好&#39;,在地址$00419630,有一个文字的UnicodeString&#39; Hello&#39;。这意味着常量被编译为上下文所需的类型,并且没有显式或隐式转换(即从UnicodeStringAnsiString,或从WordDouble)是必需的。

另请注意,与 true 常量相反,类型常量确实需要转换代码。该单词加载为movzx eax,[$0041d5a0],即从Word转换为DWord,然后存储为DWord,以DWord的形式加载到FPU中,然后存储为64位浮点值(Double)。这是一个你不会看到的转换,因为它不是真正的常量。

我知道有些人会说:&#34;好吧,它是UnicodeString,但是编译器首先将它转换为AnsiString。&#34;国际海事组织,就像说:&#34;汽车的原始车身是灰色的,但在销售(指定)之前,它必须涂成绿色。&#34;我宁愿说:&#34;出售的汽车是绿色的,无论它是如何获得这种颜色的。&#34;当然,文字和常量(IOW,const部分中定义的符号)将具有默认类型,在查询时给出并且没有请求特定类型,但用于所有实际目的 ,如果您只是将它们视为&#34;带有名称标签&#34;的文字,而不是属于特定类型,则最简单,IOW 实际类型取决于上下文< / strong>即可。这是怎么回事。

我认为大卫和我的意思相同,但用不同的词语和不同的词语。他说:它有类型A,但它被编译器转换为类型B并且结果被编译。这正是我所说的&#34;类型取决于上下文。&#34;无需转换。分配&#34;类型UInt16&#34;的常量时可以看到相同的情况。到Double:不需要转换,它直接编译为64位值并存储在Double中。没有明确或暗示的转换。因此,在这种情况下,声明为$1234的常量具有64位浮点值,而不是UInt16

键入的常量

正如您所看到的,类型化常量的代码是1到1等效于变量的代码,表明类型化常量可以被视为&#34;不可变变量&#34;,即使该术语并不存在。

另请注意,真常量甚至没有地址。您可以自己轻松尝试:

if @CInt = nil then;

您将看到它无法编译(Variable required错误)。但现在尝试:

if @CTInt = nil then;

没有这样的错误,这也支持类型化常量是&#34;不可变变量&#34;。