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

gwg*_*gwg 114 c

我正在阅读K&R的"The C Programming Language"并且发现了这个声明[Introduction,p.3]:

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

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

Die*_*Epp 143

是的,有直接支持的数据类型.

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

float x = 1.0f, y = 2.0f;
return x + y;
Run Code Online (Sandbox Code Playgroud)

它被翻译成这样的东西:

unsigned x = 0x3f800000, y = 0x40000000;
return _float_add(x, y);
Run Code Online (Sandbox Code Playgroud)

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

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

其他语言

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

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

控制结构

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

  • 基本上只是指针...相反,基本上只是原始的内存块.即使那是挑剔,但答案仍然是好的. (2认同)
  • 您可以认为空终止字符串具有"硬件支持",因为字符串终结符适合大多数处理器的"跳转为零"操作,因此比其他可能的字符串实现稍快. (2认同)

Joh*_*man 37

实际上,我敢打赌,自从1978年Kernighan和Ritchie在本书第一版中首次编写这些内容以来,这篇介绍的内容并未发生太大变化,他们将当时的历史和演变称为现代实现.

计算机基本上只是存储库和中央处理器,每个处理器使用机器代码运行; 每个处理器的部分设计是一个指令集架构,称为汇编语言,它将一组人类可读的助记符一对一映射到机器代码,这是所有数字.

而B和BCPL语言立即它之前 - - C语言的作者们在定义在被认为是在有效地编入大会尽可能的语言结构......其实意图,他们被限制被迫目标硬件.至于其他的答案已经指出,这涉及分支(GOTO和C以外的流量控制),移动(分配),逻辑运算(&| ^),基本的算术运算(加,减,递增,递减),以及内存寻址(指针).一个很好的例子是C中的前/后递增和递减运算符,据说肯定是由汤姆森加入B语言,因为它们能够在编译后直接转换为单个操作码.

这就是作者所说的"大多数计算机直接支持"的意思.他们并不是说其他语言包含在了类型和结构直接支持-它们意味着,通过设计 C构造的翻译直接(有时字面上直接)进入议会.

这种密切关系到底层大会,同时还提供所有结构化编程所需的元素,是什么导致了C语言的早期采用,什么今天保持一个流行的语言在编译的代码效率仍然是关键的环境.

有关该语言历史的有趣文章,请参阅C语言的发展 - Dennis Ritchie


Ice*_*ind 14

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

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

int myInt = 10;
Run Code Online (Sandbox Code Playgroud)

会在汇编中翻译成这样的东西:

myInt dw 1
mov myInt,10
Run Code Online (Sandbox Code Playgroud)

将此与C++相比较:

MyClass myClass;
myClass.set_myInt(10);
Run Code Online (Sandbox Code Playgroud)

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

如果没有用汇编语言实际创建程序,纯C可能是你可以制作程序的"最瘦"和"最紧凑"的代码.

编辑

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

#include <stdio.h>

void main()
{
    int myInt=10;

    printf("%d\n", myInt);
}
Run Code Online (Sandbox Code Playgroud)

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

gcc -S -O2 test.c
Run Code Online (Sandbox Code Playgroud)

以下是生成的汇编语言:

    .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
Run Code Online (Sandbox Code Playgroud)

然后我创建一个名为"test.cpp"的文件,它定义了一个类并输出与"test.c"相同的东西:

#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;
}
Run Code Online (Sandbox Code Playgroud)

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

g++ -O2 -S test.cpp
Run Code Online (Sandbox Code Playgroud)

这是生成的程序集文件:

    .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
Run Code Online (Sandbox Code Playgroud)

您可以清楚地看到,生成的程序集文件在C++文件上要大得多,然后它就在C文件中.即使你删除了所有其他的东西,只是将C"main"与C++"main"进行比较,还有很多额外的东西.

  • "C++代码"不是C++.而真正的代码如C++中的`MyClass myClass {10}`很可能编译成完全相同的程序集.现代C++编译器已经消除了抽象惩罚.结果,他们经常可以击败C编译器.例如,C的`qsort`中的抽象惩罚是真实的,但C++的`std :: sort`在基本优化之后没有抽象惩罚. (14认同)

Pet*_*des 7

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

有一个引用:

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

(在这里找到.我以为我记得一种不同的变化,比如"汇编语言的速度与汇编语言的方便性和表现力".)

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

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

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

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

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

字符串只是数组

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

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

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


ter*_*ary 6

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

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

C封装了汇编的基础知识(处理器的语言).可能是一个更真实的声明而不是"因为大多数计算机都直接支持C提供的数据类型和控制结构"


Lut*_*elt 5

谨防误导比较

  1. The statement relies on the notion of a "run-time library", which has mostly gone out of fashion since, at least for mainstream high-level languages. (It is still relevant for the smallest embedded systems.) The run-time is the minimal support a program in that language requires to execute when you use only constructs built into the language (as opposed to explicitly calling a function provided by a library).
  2. In contrast, modern languages tend not to discriminate between the run-time and the standard library, the latter often being quite extensive.
  3. At the time of the K&R book, C did not even have a standard library. Rather, the available C libraries differed quite a bit between different flavors of Unix.
  4. For understanding the statement you should not compare to languages with a standard library (such as Lua and Python mentioned in other answers), but to languages with more built-in constructs (such as old-day LISP and old-day FORTRAN mentioned in other answers). Other examples would be BASIC (interactive, like LISP) or PASCAL (compiled, like FORTRAN) which both have (among other things) input/output features built right into the language itself.
  5. In contrast, there is no standard way to get the computation results out from a C program that is using only the run-time, not any library.


Dav*_*ary 5

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

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

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

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

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

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

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

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

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

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

大多数编写编译器的人都将指令用于操作"内置于语言中"的所有数据类型到其运行时库中.因为C没有任何上述数据类型,操作和控制结构内置于该语言中,所以C运行时库中都没有包含它们 - 这使得C运行时库比运行时更小 -其他编程语言的时间库,内置了该语言的更多内容.

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