我正在阅读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++运行时必须遍历堆栈并调用析构函数和异常处理程序.
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"进行比较,还有很多额外的东西.
K&R意味着大多数C表达式(技术含义)映射到一个或几个汇编指令,而不是函数调用支持库.通常的例外是没有硬件div指令的架构上的整数除法,或没有FPU的机器上的浮点.
有一个引用:
C将汇编语言的灵活性和强大功能与汇编语言的用户友好性相结合.
(在这里找到.我以为我记得一种不同的变化,比如"汇编语言的速度与汇编语言的方便性和表现力".)
一些更高级别的语言定义其数据类型的确切宽度,并且所有计算机上的实现必须相同.但不是C.
如果你想在x86-64上使用128位整数,或者在任意大小的一般情况下使用BigInteger,你需要一个函数库.所有CPU现在都使用2s补码作为负整数的二进制表示,但即使在设计C时也不是这样.(这就是为什么在C标准中,某些能够在非2s补码机器上产生不同结果的东西在技术上是不明确的.)
如果您想要重新计算引用,则必须自己完成.如果您希望c ++虚拟成员函数根据指针所指向的对象类型调用不同的函数,那么C++编译器必须生成的不仅仅是call具有固定地址的指令.
在库函数之外,提供的唯一字符串操作是读/写字符.没有连字,没有子串,没有搜索.(字符串存储为'\0'8位整数的nul-terminated()数组,而不是指针+长度,因此要获得子字符串,您必须将nul写入原始字符串.)
CPU有时具有设计用于字符串搜索功能的指令,但仍然通常在循环中处理每个指令执行一个字节.(或者使用x86 rep前缀.也许如果C是在x86上设计的,字符串搜索或比较将是本机操作,而不是库函数调用.)
许多其他答案给出了本机不支持的事例,例如异常处理,哈希表,列表.K&R的设计理念是C本身没有这些原因的原因.
进程的汇编语言通常涉及跳转(转到),语句,移动语句,二进制关节炎(XOR,NAND,AND OR等),内存字段(或地址).将内存分为两种类型,指令和数据.这是关于所有汇编语言的(我相信汇编程序员会认为它还有更多的东西,但它归结为一般).C非常类似于这种简单性.
C是将代数组合成算术的.
C封装了汇编的基础知识(处理器的语言).可能是一个更真实的声明而不是"因为大多数计算机都直接支持C提供的数据类型和控制结构"
是否存在计算机不直接支持的数据类型或控制结构的示例?
C语言中的所有基本数据类型及其操作都可以通过一个或几个机器语言指令实现而无需循环 - 它们(几乎每个)CPU都直接支持它们.
几种流行的数据类型及其操作需要许多机器语言指令,或者需要迭代某些运行时循环,或两者兼而有之.
许多语言对这些类型及其操作都有特殊的缩写语法 - 在C中使用这种数据类型通常需要输入更多代码.
此类数据类型和操作包括:
所有这些操作都需要几十种机器语言指令,或者需要在几乎每个处理器上迭代一些运行时循环.
一些流行的控制结构也需要许多机器语言指令或循环包括:
无论是用C语言还是其他语言编写,当程序操作这些数据类型时,CPU最终必须执行操作这些数据类型所需的任何指令.这些说明通常包含在"库"中.每种编程语言,甚至C,都为每个平台都有一个"运行时库",默认情况下包含在每个可执行文件中.
大多数编写编译器的人都将指令用于操作"内置于语言中"的所有数据类型到其运行时库中.因为C没有任何上述数据类型,操作和控制结构内置于该语言中,所以C运行时库中都没有包含它们 - 这使得C运行时库比运行时更小 -其他编程语言的时间库,内置了该语言的更多内容.
当程序员想要一个程序 - 用C语言或他选择的任何其他语言 - 来操作其他不是"内置于语言中"的数据类型时,程序员通常会告诉编译器在该程序中包含其他库,或者有时("避免依赖")直接在程序中编写这些操作的另一个实现.