C数据类型如何“大多数计算机直接支持”?

时间:2015-01-16 04:55:19

标签: c

我正在阅读K& R的“C编程语言”并且发现了这个声明[Introduction,p。 3]:

  

由于大多数计算机直接支持C提供的数据类型和控制结构,因此实现自包含程序所需的运行时库很小。

粗体声明是什么意思?是否存在计算机直接支持 的数据类型或控制结构的示例?

12 个答案:

答案 0 :(得分:143)

是的,有些数据类型不受直接支持。

在许多嵌入式系统上,没有硬件浮点单元。所以,当你编写这样的代码时:

float x = 1.0f, y = 2.0f;
return x + y;

它被翻译成这样的东西:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);

然后编译器或标准库必须提供_float_add()的实现,它占用嵌入式系统的内存。如果你在一个非常小的系统上计算字节数,可以加起来。

另一个常见示例是自1999年以来C标准中的64位整数(long long),32位系统不直接支持这些整数。旧的SPARC系统不支持整数乘法,因此乘法必须由运行时提供。还有其他例子。

其他语言

相比之下,其他语言有更复杂的原语。

例如,Lisp符号需要大量的运行时支持,就像Lua中的表,Python中的字符串,Fortran中的数组等等。 C中的等价类型通常要么根本不是标准库的一部分(没有标准符号或表),要么它们更简单并且不需要太多的运行时支持(C中的数组基本上只是指针,nul-terminated字符串是几乎一样简单)。

控制结构

C中缺少值得注意的控制结构是异常处理。非本地退出仅限于setjmp()longjmp(),它只保存和恢复处理器状态的某些部分。相比之下,C ++运行时必须遍历堆栈并调用析构函数和异常处理程序。

答案 1 :(得分:37)

实际上,我敢打赌,自1978年Kernighan和Ritchie首次在本书的第一版中写下这些介绍以来,这一介绍的内容并未发生太大变化,他们提到了这一介绍的历史和演变。 C当时比现代实施更多。

计算机基本上只是内存库和中央处理器,每个处理器都使用机器代码运行;每个处理器的部分设计是一个指令集架构,称为Assembly Language,它将一组人类可读的助记符一对一映射到机器码,这些都是数字。

C语言的作者 - 以及紧接其之前的B和BCPL语言 - 意图定义语言中尽可能有效汇编到汇编中的结构......事实上,他们被迫受到限制在目标硬件中。正如其他答案所指出的,这涉及分支(GOTO和C中的其他流控制),移动(赋值),逻辑运算(& | ^),基本算术(加,减,递增,递减)和内存寻址(指针)。一个很好的例子是C语言中的前/后递增和递减运算符,据说这些运算符被Ken Thompson添加到B语言中,因为它们能够在编译后直接转换为单个操作码。

这就是作者在大多数计算机直接支持时所说的意思"。他们并不意味着其他语言包含直接支持的类型和结构 - 它们意味着按设计 C构造已翻译大多数直接(有时字面直接)到Assembly中。

与底层Assembly的这种紧密关系,虽然仍然提供了结构化编程所需的所有元素,但却导致了C的早期采用,以及在编译代码效率的环境中使它成为当今流行语言的原因还是关键。

有关该语言历史的有趣文章,请参阅The Development of the C Language - Dennis Ritchie

答案 2 :(得分:14)

简而言之,目标计算机的微处理器也支持C支持的大多数语言结构,因此,编译后的C代码可以非常有效地转换为微处理器的汇编语言,从而导致代码更小,占用空间更小。

较长的答案需要一点汇编语言知识。在C中,这样的陈述:

int myInt = 10;

会在汇编中转换为类似的内容:

myInt dw 1
mov myInt,10

将此与C ++相比较:

MyClass myClass;
myClass.set_myInt(10);

生成的汇编语言代码(取决于MyClass()的大小)可以添加数百个汇编语言行。

如果没有用汇编语言实际创建程序,纯C可能是最简单的"最简单的"并且" tightest"代码,你可以制作一个程序。

修改

鉴于对我的回答的评论,我决定进行一项测试,仅仅是为了我自己的理智。我创建了一个名为" test.c"的程序,它看起来像这样:

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}

我使用gcc将其编译为汇编。我使用以下命令行来编译它:

gcc -S -O2 test.c

以下是生成的汇编语言:

    .file   "test.c"
    .section    .rodata.str1.1,"aMS",@progbits,1
.LC0:
    .string "%d\n"
    .section    .text.unlikely,"ax",@progbits
.LCOLDB1:
    .section    .text.startup,"ax",@progbits
.LHOTB1:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB24:
    .cfi_startproc
    movl    $10, %edx
    movl    $.LC0, %esi
    movl    $1, %edi
    xorl    %eax, %eax
    jmp __printf_chk
    .cfi_endproc
.LFE24:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE1:
    .section    .text.startup
.LHOTE1:
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits
然后我创建了一个名为&#34; test.cpp&#34;的文件。它定义了一个类并输出与&#34; test.c&#34;:

相同的东西
#include <iostream>
using namespace std;

class MyClass {
    int myVar;
public:
    void set_myVar(int);
    int get_myVar(void);
};

void MyClass::set_myVar(int val)
{
    myVar = val;
}

int MyClass::get_myVar(void)
{
    return myVar;
}

int main()
{
    MyClass myClass;
    myClass.set_myVar(10);

    cout << myClass.get_myVar() << endl;

    return 0;
}

我使用以下命令以相同的方式编译它:

g++ -O2 -S test.cpp

以下是生成的程序集文件:

    .file   "test.cpp"
    .section    .text.unlikely,"ax",@progbits
    .align 2
.LCOLDB0:
    .text
.LHOTB0:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9set_myVarEi
    .type   _ZN7MyClass9set_myVarEi, @function
_ZN7MyClass9set_myVarEi:
.LFB1047:
    .cfi_startproc
    movl    %esi, (%rdi)
    ret
    .cfi_endproc
.LFE1047:
    .size   _ZN7MyClass9set_myVarEi, .-_ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE0:
    .text
.LHOTE0:
    .section    .text.unlikely
    .align 2
.LCOLDB1:
    .text
.LHOTB1:
    .align 2
    .p2align 4,,15
    .globl  _ZN7MyClass9get_myVarEv
    .type   _ZN7MyClass9get_myVarEv, @function
_ZN7MyClass9get_myVarEv:
.LFB1048:
    .cfi_startproc
    movl    (%rdi), %eax
    ret
    .cfi_endproc
.LFE1048:
    .size   _ZN7MyClass9get_myVarEv, .-_ZN7MyClass9get_myVarEv
    .section    .text.unlikely
.LCOLDE1:
    .text
.LHOTE1:
    .section    .text.unlikely
.LCOLDB2:
    .section    .text.startup,"ax",@progbits
.LHOTB2:
    .p2align 4,,15
    .globl  main
    .type   main, @function
main:
.LFB1049:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $10, %esi
    movl    $_ZSt4cout, %edi
    call    _ZNSolsEi
    movq    %rax, %rdi
    call    _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
    xorl    %eax, %eax
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    ret
    .cfi_endproc
.LFE1049:
    .size   main, .-main
    .section    .text.unlikely
.LCOLDE2:
    .section    .text.startup
.LHOTE2:
    .section    .text.unlikely
.LCOLDB3:
    .section    .text.startup
.LHOTB3:
    .p2align 4,,15
    .type   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, @function
_GLOBAL__sub_I__ZN7MyClass9set_myVarEi:
.LFB1056:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $_ZStL8__ioinit, %edi
    call    _ZNSt8ios_base4InitC1Ev
    movl    $__dso_handle, %edx
    movl    $_ZStL8__ioinit, %esi
    movl    $_ZNSt8ios_base4InitD1Ev, %edi
    addq    $8, %rsp
    .cfi_def_cfa_offset 8
    jmp __cxa_atexit
    .cfi_endproc
.LFE1056:
    .size   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi, .-_GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .section    .text.unlikely
.LCOLDE3:
    .section    .text.startup
.LHOTE3:
    .section    .init_array,"aw"
    .align 8
    .quad   _GLOBAL__sub_I__ZN7MyClass9set_myVarEi
    .local  _ZStL8__ioinit
    .comm   _ZStL8__ioinit,1,1
    .hidden __dso_handle
    .ident  "GCC: (Ubuntu 4.9.1-16ubuntu6) 4.9.1"
    .section    .note.GNU-stack,"",@progbits

正如您可以清楚地看到的,生成的程序集文件在C ++文件上要大得多,然后它就在C文件中。即使你删除了所有其他东西,只需比较C&#34; main&#34;对于C ++&#34; main&#34;,还有很多额外的东西。

答案 3 :(得分:7)

K&amp; R意味着大多数C表达式(技术含义)映射到一个或几个汇编指令,而不是函数调用支持库。通常的例外是没有硬件div指令的架构上的整数除法,或没有FPU的机器上的浮点。

有一句话:

  

C将汇编语言的灵活性和强大功能与汇编语言的用户友好性相结合。

found here。我以为我记得有一种不同的变化,比如#34;汇编语言的速度以及汇编语言的方便性和表现力&#34;。

long int通常与本机注册的宽度相同。

某些更高级别的语言定义了数据类型的确切宽度,并且所有计算机上的实现必须相同。但不是C.

如果你想在x86-64上使用128位整数,或者在任意大小的一般情况下使用BigInteger,你需要一个函数库。现在所有CPU都使用2s补码作为负整数的二进制表示,但即使在设计C时也不是这样。 (这就是为什么在非标准补充机器上产生不同结果的某些东西在C标准中在技术上是不明确的。)

指向数据或函数的C指针与汇编地址的工作方式相同。

如果您想要重新计算的参考文献,您必须自己完成。如果您希望c ++虚拟成员函数根据指针所指向的对象类型调用不同的函数,那么C ++编译器必须生成的不仅仅是具有固定地址的call指令。

字符串只是数组

在库函数之外,提供的唯一字符串操作是读/写字符。没有连字,没有子串,没有搜索。 (字符串存储为8位整数的nul终止('\0')数组,而非指针+长度,因此要获得子字符串,您必须将nul写入原始字符串。)

CPU有时会有一些设计用于字符串搜索功能的指令,但通常在循环中每个指令执行一个字节。 (或者使用x86 rep前缀。也许如果C是在x86上设计的,字符串搜索或比较将是本机操作,而不是库函数调用。)

许多其他答案给出了本机不支持的事例,例如异常处理,哈希表,列表。 K&amp; R的设计理念是C本身没有这些原因的原因。

答案 4 :(得分:6)

进程的汇编语言通常涉及跳转(转到),语句,移动语句,二进制关节炎(XOR,NAND,AND OR等),内存字段(或地址)。将内存分为两种类型,指令和数据。这是关于所有汇编语言的(我相信汇编程序员会认为它还有更多的东西,但它归结为一般)。 C非常类似于这种简单性。

C是将代数组合成算术的。

C封装了汇编的基础知识(处理器的语言)。可能是一个比#34更真实的陈述;因为C提供的数据类型和控制结构直接由大多数计算机支持&#34;

答案 5 :(得分:5)

谨防误导比较

  1. 该声明依赖于&#34;运行时库&#34; 概念,至少对于主流高级语言而言,这已经过时了。 (它仍然与最小的嵌入式系统相关。)运行时是当您仅使用内置于语言中的构造时(而不是显式调用库提供的函数),该语言需要执行的最小支持。
  2. 相比之下,现代语言往往不会区分运行时和标准库,后者往往相当广泛。
  3. 在K&amp; R出版时, C甚至没有标准。相反,可用的C库在不同版本的Unix之间存在很大差异。
  4. 为了理解你的声明,不应该与具有标准库的语言(例如其他答案中提到的Lua和Python)进行比较,而是用于具有更多内置结构的语言(例如old-日LISP和旧日FORTRAN在其他答案中提到)。其他示例包括BASIC(交互式,如LISP)或PASCAL(已编译,如FORTRAN),它们都具有内置于语言本身的输入/输出功能(除其他外)。
  5. 相比之下,没有标准的方法可以从仅使用运行时的C程序中获取计算结果,而不是任何库。

答案 6 :(得分:5)

  

是否存在数据类型或控制结构的示例   是否由计算机直接支持?

C语言中的所有基本数据类型及其操作都可以通过一个或几个机器语言指令实现而无需循环 - 它们(几乎每个)都直接支持。

几种流行的数据类型及其操作需要许多机器语言指令,或者需要迭代某些运行时循环,或两者兼而有之。

许多语言对这些类型及其操作都有特殊的缩写语法 - 在C中使用这些数据类型通常需要输入更多代码。

此类数据类型和操作包括:

  • 任意长度的文本字符串操作 - 连接,子字符串,将新字符串分配给用其他字符串初始化的变量等(&#39; s =&#34; Hello World!&#34 ;; s =(s + s)[2:-2]&#39;在Python中)
  • 具有嵌套虚拟析构函数的对象,如C ++和其他所有面向对象的编程语言
  • 2D矩阵乘法和除法;解决线性系统(&#34; C = B / A; x = A \ b&#34;在MATLAB和许多阵列编程语言中)
  • 正则表达式
  • 可变长度数组 - 特别是,将项附加到数组的末尾,(有时)需要分配更多内存。
  • 读取在运行时改变类型的变量的值 - 有时它是浮点数,有时它是一个字符串
  • 关联数组(通常称为&#34;地图&#34;或&#34;字典&#34;)
  • 列表
  • 比率(&#34;(+ 1/3 2/7)&#34;给出&#34; 13/21&#34; in Lisp
  • 任意精度算术(通常称为&#34; bignums&#34;)
  • 将数据转换为可打印的表示形式(JavaScript中的&#34; .tostring&#34;方法)
  • 饱和定点数(通常用于嵌入式C程序)
  • 在运行时评估输入的字符串,就好像它是一个表达式(&#34; eval()&#34;在许多编程语言中)。

所有这些操作都需要几十种机器语言指令,或者需要在几乎每个处理器上迭代一些运行时循环。

一些流行的控制结构也需要许多机器语言指令或循环包括:

  • 延续
  • 例外
  • 懒惰的评价

无论是用C语言还是其他语言编写,当程序操作这些数据类型时,CPU最终必须执行操作这些数据类型所需的任何指令。 这些说明通常包含在&#34;库&#34;中。 每种编程语言,甚至C,都有一个&#34;运行时库&#34;对于每个可执行文件中默认包含的每个平台。

编写编译器的大多数人都会使用指令来操作所有数据类型,这些数据类型已经内置到语言中。进入他们的运行时库。 因为C没有任何上述数据类型以及语言中内置的操作和控制结构,所以它们都不包含在C运行时库中 - 这使得C运行时库比其他编程语言的运行时库小,后者有更多内置于该语言的内容。

当一个程序员想要一个程序 - 用C语言或他选择的任何其他语言 - 来操作那些不是&#34;内置于语言中的其他数据类型时,该程序员通常会告诉编译器包含使用该程序的其他库,或者有时(对于&#34;避免依赖&#34;)直接在程序中编写这些操作的另一个实现。

答案 7 :(得分:4)

C中有哪些内置数据类型? 它们包括intchar* intfloat,数组等... CPU可以理解这些数据类型。 CPU知道如何使用数组,如何取消引用指针以及如何对指针,整数和浮点数执行算术运算。

但是当你使用更高级的编程语言时,你已经构建了抽象数据类型和更复杂的结构。例如,查看C ++编程语言中的大量内置类。 CPU不了解类,对象或抽象数据类型,因此C ++运行时填补了CPU和语言之间的空白。这些是大多数计算机不直接支持的数据类型的示例。

答案 8 :(得分:4)

这取决于计算机。在发明C的PDP-11上,long得不到很好的支持(你可以购买一个可选的附加模块,它支持一些但不是全部的32位操作)。在任何16位系统上都有不同程度的情况,包括原始的IBM PC。同样,对于32位机器或32位程序上的64位操作,尽管K&amp; R书中的C语言根本没有任何64位操作。当然,在80年代和90年代[包括386和486处理器],甚至今天的一些嵌入式系统中,有许多系统没有直接支持浮点运算(floatdouble )。

对于一个更奇特的例子,一些计算机体系结构仅支持“面向字的”指针(指向内存中的双字节或四字节整数)和字节指针(char *或{{1} })必须通过添加额外的偏移字段来实现。 This question详细介绍了此类系统。

它所指的“运行时库”函数不是您将在手册中看到的函数,而是these, in a modern compiler's runtime library之类的函数,它们用于实现不<的基本类型操作/ em>由机器支持。 K&amp; R本身所指的运行时库可以在The Unix Heritage Society's website找到 - 你可以看到像void *这样的函数(不同于同名的C函数,当时不存在)用于实现32位值的划分,PDP-11即使使用附加组件也不支持,而ldiv(和csv也在csv.c中)保存和恢复在堆栈上注册以管理调用和函数返回。

他们可能也指他们选择不支持底层机器不直接支持的许多数据类型,这与其他当代语言(如FORTRAN)不同,后者的数组语义也没有映射到CPU的底层指针支持为C的数组。事实上C数组总是零索引并且在所有等级中始终具有已知大小,但第一个意味着不需要存储数组的索引范围或大小,也不需要运行库函数来访问它们 - 编译器可以简单地硬编码必要的指针算法。

答案 9 :(得分:3)

该陈述仅仅意味着C中的数据和控制结构是面向机器的。

这里有两个方面需要考虑。一个是C语言有一个定义(ISO标准),它允许自由地定义数据类型的方式。这意味着 C语言实现是为机器定制的。 C编译器的数据类型与编译器所针对的机器中可用的数据类型相匹配,因为该语言具有纬度。如果机器具有不常见的字大小(如36位),则可以使类型intlong符合该类型。假设int正好是32位的程序将会中断。

其次,由于这种便携性问题,还有第二个影响。在某种程度上,K&amp; R中的陈述已成为一种自我实现的预言,或者可能是相反的。也就是说,新处理器的实现者意识到支持C编译器的迫切需要,并且他们知道存在许多C代码,假设每个处理器看起来像80386&#34;。架构在设计时考虑到了C:不仅考虑了C,而且还考虑到了关于C可移植性的常见误解。您根本无法引入具有9位字节的机器或其他任何用于通用目的的机器。假设类型char正好是8位宽的程序将会中断。只有一些由可移植性专家编写的程序才能继续工作:可能还不足以通过合理的努力将完整的系统与工具链,内核,用户空间和有用的应用程序结合在一起。换句话说,C类型看起来像是硬件可用的类型,因为硬件看起来像是许多非便携式C程序的其他硬件。

  

是否存在计算机不直接支持的数据类型或控制结构的示例?

许多机器语言不直接支持数据类型:多精度整数;链表;哈希表;字符串。

大多数机器语言不直接支持控制结构:第一类继续;协同程序/线程;发电机;异常处理。

所有这些都需要使用大量通用指令和更基本的数据类型创建大量的运行时支持代码。

C有一些标准数据类型,某些机器不支持这些类型。由于C99,C具有复数。它们由两个浮点值组成,并与库例程一起使用。有些机器根本没有浮点装置。

关于某些数据类型,目前尚不清楚。如果机器支持使用一个寄存器作为基址寻址存储器,则支持另一个寄存器 作为缩放位移,这是否意味着数组是直接支持的数据类型?

另外,谈到浮点,有标准化:IEEE 754浮点。为什么你的C编译器有double同意处理器支持的浮点格式,这不仅是因为两者都是一致的,而是因为这种表示有一个独立的标准。

答案 10 :(得分:2)

诸如

之类的东西
  • 列表几乎用于所有函数语言。

  • <强>例外

  • 关联数组(地图) - 包含在例如PHP和Perl。

  • 垃圾收集

  • 数据类型/控制结构包含在多种语言中,但不受CPU直接支持。

答案 11 :(得分:2)

直接支持应理解为有效映射到处理器的指令集。

  • 直接支持整数类型是规则,除了long(可能需要扩展的算术例程)和short size(可能需要屏蔽)。

  • 直接支持浮点类型需要FPU可用。

  • 对位字段的直接支持非常出色。

  • 结构和数组需要地址计算,在某种程度上直接支持。

  • 指针总是通过间接寻址直接支持。

  • goto / if / while / for / do由无条件/条件分支直接支持。

  • 当跳转表适用时,可以直接支持切换。

  • 通过堆栈功能直接支持函数调用。