即使新闻稿、表述、表达式和调用都和 校对基本原理 相关,因此标准答案就在 C++的输入 上。
一、consideration
1.c:
$ cc 1.c
1.c:2:5: error: use of undeclaredidentifiera
a = 1;
^
1 error generated.请稿子写作C++的输入。说三遍,请稿子写作那个输入。
特别注意这儿有位词汇叫 undeclared
use of: 采用。谁使用?你,ahh,【你在采用两个】表达式。undeclared:declare 是【对内即告】,undeclared属格 –【没对内即告过的】,文绉绉的讲法,叫【consideration】identifier: identify是【身分证】,也叫ID。*ier是甚么人,identifier是【有身分的人】,文绉绉的讲法,叫【URL】合出来是,【你在采用两个】【没对内即告过的】【身分证】a。
这句话是说,a 标记符没新闻稿,不晓得它是个甚么小东西。
这就叫consideration undeclared
二、未表述
1.c:
$ cc 1.c
/tmp/ccxhuV7j.o: In function `main:
1.c:(.text+0x6): undefined reference to `a
collect2: error: ld returned 1 exit status请稿子阅读C++的输入。特别注意这儿有位词汇叫undefined
undefined: 【没表述过的】意思reference:【介绍信】的意思,这儿有位文绉绉的讲法,叫【引用】to a: 对于 a合出来是,对于 a 的【介绍信】,是【没表述过的】
这句话是说,a 是个名称,它引用的内存实体是不存在的。a 为甚么一定要引用两个内存实体?哈哈,你一下子就摸到的计算机语言的灵魂,为了行文的需要,那个疑问不能在这儿展开,即使标准答案太长了,本文倒数的4节是那个问题的标准答案。
这就叫未表述 undefined
三、不能表达式
1.c:
$ cc 1.c
1.c:2:8: error: non-object type int () is not assignable
main = 123;
~~~~ ^
1 error generated.请稿子写作C++的输入。特别注意这儿有位词汇叫 assignable
这句话是说
non-object type: 非对象类型int (): 返回值为int的函数类型is not assignable: 不可以被表达式合出来是,非对象类型的函数类型不可以被表达式
main 是返回值为int的函数类型,它为甚么不能被表达式呢?
要从对象说起,对象是一块可以操作的内存块。
言外之意,内存中还存在不能被操作的内存?哈哈,你的认知决定了你的高度,是的,内存有
向量区 禁止入内文本区 禁止乱涂乱画数据区 自己的可以随便玩,不是自己的禁止拍照IO区 只开放给专家学者对象!两个多么熟悉多么丰富的词汇。。。
本例中,main 是一块文本区的内存,不是可操作的内存,因此不能被表达式。
这就叫不能表达式 not assignable
四、不能调用
1.c:
$ cc -w 1.c
1.c:1:9: error: initializer element is not computable at load time
int a = “foo”;
^请稿子写作C++的输入。特别注意这儿有位词汇叫 initializer
element:是常量”foo”,是个字符串地址
is not computable:不是算数
at load time:在程序运行的时候
合出来是,字符串地址在运行时不能被计算。额,C++,你管的可真多。
a 那个位置是 int ,字符串是两个地址,也是 int 。因此,从基本原理上来说上面程序没问题。事实上,在老 c 语言中,上述程序正常。
但是,后来语法变了。为甚么?为了更加规范和安全,这种行为被禁止了。C++给出的理由是,调用的元素在运行时是算不了的。其实,这是两个善意的谎言,指针当然可以计算。但是为了规范有人阻止了你,阻止你的人正是调用。调用是C++那个大程序的两个子程序。
程序分校对时,运行时。目前看来,校对时越来越庞大,越来越聪明。各种思想方法论被发明出来,典型如c++。
事实上,C++调用的一些小聪明展现了他的可怜父母心,巴不得把所有后事都料理完,脏活累活全都不让孩子干。
比如下面的代码
1.c:
C++为”bar”分配字符串存储,把days变成数组,hour算成600000,而不是在运行时再算。
这是调用器 initializer,两个校对逻辑
五、九年前的两个心结
不晓得三年后,那个标准答案你是否还看。
但是,起码,那个标准答案了结了我九年前的两个心结。
在2009年左右,我爱去 chinaunix (当时叫CU)上闲逛,那个论坛以脾气大,炮火猛著称。
有次我调侃了某位仁兄,没想到他立马查阅了我以往所有的提问和回答,抓到了我两个小辫子。
那个小辫子是,在某个提问中,我提到对调用的机制不太清楚。
他当即骂我,连调用都不懂,还在这儿讨论个XX,装甚么装,滚!!!
一时间,我百感交集,
最近在另一篇帖子中的提问来充当证据。
另一方面,我为自己不晓得调用的底层细节而感到脸红和委屈。
调用到底是怎么回事,为甚么我一直不能调查清楚呢。
那个问题就像哥德巴赫猜想一样,看着简单,实际却无法证明。
真是哑巴吃黄莲,有苦说不出。我当时只是感觉那个问题很难,但是不敢肯定。
我一时间很受伤,慢慢远离了 CU,也慢慢远离了 C 语言,而我对那个问题依然不甚清楚。
很多年过去了,在一次不成功的项目中,无心插柳柳成荫,明白了调用的来龙去脉。
六、程序
在讨论新闻稿、表述、表达式和调用这些概念之前,必须要对程序的历史做一些回顾。
目的是要建立两个信念,程序最好是那个样子(是现在我们看到这样),或者干脆说必须是那个样子,要不然就没法跟机器打交道。
在二战期间雷达的采用,随后计算机的发明,IBM大型机的研发,以及人类登月,“编程活动”在实践中不断的涌现出种种方法论,编程这门技术日趋成熟。
小型机出现了,它培养并涌现出一批黑客。
后来,桌面机的出现,更是让这门技术开始流向民间,后面的故事大家都熟悉了,比尔盖茨和乔布斯把握住了机会。
回到 1970 年代,即使在 C 语言被发明之前,人们已经意识到了很多宝贵的经验、教条和技巧,C 语言和 UNIX 系统是站在这些巨人的肩膀上建立出来的。
这儿只列举几条和本话题相关的几个“肩膀”:
1. File 文件
比如书籍,人类天生喜欢把信息记录在两个个文件中,这是个看似再自然不过的特征。
而为甚么是文件,而不是其他形式,比如岩画、结绳记事,或者更猛的诸如爪子抓痕、尿液标记。
标准答案你懂的,即使文件信息量大,传播方便。结绳记事成本高,时间长就绳子不够了。岩画能被外借写作吗,不能。因此,当 1956 年 IBM 发明硬盘后,就有人琢磨着把文件在硬盘中表示出来。
大约在 1961 年,方案终于出来了,那个方案叫“文件系统”,每个文件有位名字,可以被看到,它会给你一种假象,仿佛在桌子上看到一个个的纸质文件。
你可以打开它,修改它,关闭它。就像我们写一封信一样,先打开信封,写信,再把它装回信封。
即使程序员选择了用文件去编写程序,并且一直沿用至今,因此文件对程序的影响可以说是方方面面。
2. Virtual Memory 虚拟内存
虚拟内存这项计算机的特征,很大程序上影响了程序(是供机器执行的那个小东西)的组织结构。
它极其难以理解,并且追踪出来非常不便。但是为甚么非要采用它呢?
在1940、1950年代,人们编写大型程序会被内存管理的问题困扰,比如内存覆盖这种相互干扰。
我相信,以当时的条件,调试出来简直是手足无措。
最早提出虚拟内存的是 1956 年一位德国物理学家。
虚拟内存方便了多任务、多道程序处理,但是这不是最重要的。
最重要的是,即使加强了系统对程序的控制,因此方便了人的编程活动。
它在系统和程序间架起一道硬件防火墙,使得程序崩溃不会殃及到系统。
虚拟内存如此重要,在高级编程语言发明之前它就成为了计算机重要的特征。因此,它影响了所有语言的设计。
3. Subroutine 子过程
1968 年有人提出goto有害论,随后,结构化编程成为主流。
我们今天看到的函数是子过程的一种。
函数在进入子过程前有两个备份操作,退出子过程前有两个恢复操作,并且函数和栈有密切关系,它有两个调用链。
4. Pointer 指针
1964 年被发明。
在 pdp11 小型机中,有指针和二级指针的寻址模式,直接用指令实现的。
指针形如 (r),二级指针形如 *(r)
我觉得这种写法比 *p, **p 要好点,不过挺遗憾,可能是不能表达三级指针,没被 C 采用
还有一种寻址是 (r)+ 和 -(r),即使这两种操作用的频率很高,因此没实现 +(r) 和 (r)-。
(r)+ 和 -(r) 可以望文生义,是个很好的设计。
如果 C 语言采用那个设计,就不会后来对 ++i,i++ 的诟病了吧。
5. Stack 栈
这儿说的栈并不是数据结构中前进后出那个栈,而是系统中为栈设计的一整套机制,包括两个通用寄存器和一些指令。
栈具体是甚么时候被发明的我不清楚,也许是种学术的结晶吧。
6. text&data 程序和数据
程序应该逻辑和数据分离。事实上数据被进一步细分。
在 C 种程序中,这种划分是
a) 正文区
函数的表述、汇编子过程、中断和陷入向量
在unix系统中,正文区占内核 57.45%
而
函数的表述占正文段 91.49%
子过程占正文段 7.62%
中断和陷入向量占正文段 0.86%
b) 调用数据区
下列两种情况数据会放入调用数据区
c代码中明确调用的全局表达式
汇编代码中调用的数据
在unix系统中,调用数据区占内核 3.51%
c) bss数据区
c代码中的未调用表达式
汇编代码中的未调用表达式
c代码中的静态局部表达式
在unix系统中,bss数据区占内核 39.03%
bss 意思是 block started by symbol
这句话揭示了一定真相,C++只是把它们当作符号来看待,为其安排地址。
在程序文件中它们不必存在 – 实际是是不存在,当然这是前辈故意这么设计的。
当程序执行的时候,bss 段才在内存中被安排并被清空,这件事情是操作系统或者说加载器做的。
因此,bss实体是加载器实现的,而调用数据实体是C++实现的。bss实体只在内存中,调用数据实体在程序文件中。
七、新闻稿和表述
前文提到,程序员选择用文件去编写程序,选择文件的原因也分析了。
人和文件要想到达机器那边,需要经过C++这座桥梁。
两个大型程序会被组织成多个文件,这就给校对带来了难题。
这些文件最终是要被翻译成程序的,可是它们的数量却是变化的。
较小的程序可能有2个文件,较大的程序可能有几百万个文件,甚至只要我想,我可以制造两个10亿文件的代码包。
并且两个文件就像两个画板一样,它终究是有两个大小的。就算两个文件很大很大,它仍然有位大小,你不能制造两个无穷大的文件。
C++在那个时候,接受了人类喜欢文件这一事实(这么说是想把C++拟人成一头牛 )。
代码是组织在多个文件中的,C++为了解决那个问题,很自然的就提出了新闻稿和表述这两个概念新闻稿 Declarations
表述 Definitions
两个表达式或函数在内存中只能存在一份,因此在代码中它只能在两个地方登场(登场那个词是两年后想出的),这是表述。
而那个表达式或函数可能被多个文件采用,在每个文件中它都要登场,也是每个文件中都表述,怎么解决那个矛盾呢?– 用新闻稿。
如果程序不是存放在多个文件中,那么根本就不需要新闻稿,直接采用,直接登场,根本不用关心新闻稿表述。
程序虽然放在多个文件中,如果它们能相互引用(考,那和两个文件有甚么分别),那么也不需要新闻稿。
可是,你晓得这些假设都是不可能的,即使人类是用两个两个的文件去表达的。因此,是人类喜欢文件这一特征,造成了C++必须去这么设计,必须有新闻稿和表述这种语法。
八、表达式和调用
现在的C++已经“聪明”到超出你的想象。
即使去看几十年前的老 c C++,它的聪明程度也会令你惊叹。
调用是这样的两个“聪明”的行为。
可是偏偏调用采用了和表达式一样的语法,形如
int foo = 123;
结果,导致了那个校对时行为有点耍“小聪明”的味道。
如果我告诉你,在c语言本来的设计中,调用和表达式是两种截然不同的语法。
你就会恍然大悟了。
这两种语法出现的场景、作用的对象和含义都不相同,很好区分。
调用完全是C++的行为,表达式则是运行时的行为。
标准 C 后来统一了调用那个概念,全局表达式的调用和自动表达式的默认值表达式都叫调用。
这确实更“高级”了,但是其实这两个调用差别却存在,全局表达式的调用值只能是常量。
因此这也是 C 的两个遗憾。
八、参考
K&R 1.10节 2.4节 4.3节 4.4节 4.9节 是那个话题是官方的标准答案。
K&R 这本书我十年后再次重读,让我感到我越读不懂它,这是一本计算机界的奇幻之书。
它把大量的优美的代码、算法隐藏在例子中,
晦涩的机器基本原理埋藏在它的括号,甚至是语气之内,
艰深的校对基本原理清晰的列在附录之后,
对历史的讲述、对标准微妙的嘲讽和无奈编撰在序言前言的字里行间,
对软件工程的争论用务实的代码去化解。
两位作者的友情和自谦以及对程序员的谆谆教导贯穿这本不厚也不薄的书。
这是本适合重读,不适合学习的书。
这是本不能用读书计划解决的书。
这是本拿来验证自己的书。