c语言 第一章绪论 c语言程序设计
计算机程序
分享好书《C语言程序设计:现代方法(第2版·修订版)》
《C语言程序设计》及习题集共2本-谭浩强
今天在清点还没带回老家的书籍时,发现了这本老古董,一直留在身边,在家在外工作时都很少看,带回去留给孩子以后看个基础吧,希望下一代在未来有竞争力在职场或有幸在商海拼搏。虽然也有很多其他电子版,对孩子而言太厚了不便培养兴趣。
咱这个时代到了落幕的时候了,为了生活还得漂泊努力,希望武汉光谷有机会!
daemonize:作为守护进程运行命令的工具
daemonize是一个命令行实用程序,它作为Unix/Linux 守护进程运行命令。daemonize 工具是用c语言编写的。
大多数程序被设计为作为守护进程运行。然而,你偶尔会遇到一个不是这样的。当必须运行一个守护程序,而这个守护程序没有正确地使自己成为一个真正的Unix守护程序时,可以使用daemonize强制它作为一个真正的守护程序运行。
如果你用的是Mac,可以使用homebrew来安装daemonize,如下所示:
$ brew install daemonize
谭浩强那个书真的误人子弟?当年看谭老师的C语言课本就这种感觉,1+1=2大家都会了吧?来,我们来点复杂点的,大家看看这个傅里叶变换。
但谭浩强的也在与时俱进了,最新的貌似是第五版,对比谭浩强的《C语言程序设计》第四版和《TCPL》第二版我觉得还是谭浩强的更适合国人入门,讲得更全更通俗易懂。
#程序员# 程序猿(媛)必读书籍:
第一阶段:
《C语言程序与设计》
《c++进阶宝典》
《Swift入门与实践》
《JavaScript高级程序设计》
第二阶段:
《教你怎么不生气》
《道德经》
《沉默的愤怒》
第三阶段:
《颈椎病康复指南》
《腰椎间盘突出日常护理》
《强迫症的自我恢复》
第四阶段:
《活着》
[泪奔][泪奔][泪奔]
估计以前读大学的小伙伴大学的c语言教程都是谭浩强老爷子的C程序设计吧。现在网上很多人都在骂这本书,说得好像一文不值,罪大恶极,对谭老爷子也是极尽讽刺之能事。说实话,感觉这样说是不公允的,谭老爷子在1989年C语言第一次标准化(C89)仅仅2年后,老爷子便在57岁高龄写出了《C程序设计》,还是很牛的。诚然,这本书里面有不少错误疏漏和艰难晦涩的地方,但在当时的条件下,绝对算得上是经典之作了。以现在的标准反过来去批判当年的开拓者,不考虑实际情况,无限放大缺点,是一种典型的傲慢与无知,与“何不食肉糜”一样可笑。
学过C语言的人都知道,用C语言设计的程序都有一个main函数,而且是从main函数开始执行的。Linux 0.11的代码是用C语言编写的。奇怪的是,为什么在操作系统启动时先执行的是三个由汇编语言写成的程序,然后才开始执行main函数;为什么不是像我们熟知的C语言程序那样,从main函数开始执行呢。
通常,我们用C语言编写的程序都是用户应用程序。这类程序的执行有一个重要的特征,就是必须在操作系统的平台上执行,也就是说,要由操作系统为应用程序创建进程,并把应用程序的可执行代码从硬盘加载到内存。
现在我们讨论的是操作系统,不是普通的应用程序,这样就出现了一个问题:应用程序是由操作系统加载的,操作系统该由谁加载呢?
从前面的节中我们知道,加载操作系统的时候,计算机刚刚加电,只有BIOS程序在运行,而且此时计算机处在16位实模式状态,通过BIOS程序自身的代码形成的16位的中断向量表及相关的16位的中断服务程序,将操作系统在软盘上的第一扇区(512字节)的代码加载到内存,BIOS能主动操作的内容也就到此为止了。
准确地说,这是一个约定。对于第一扇区代码的加载,不论是什么操作系统都是一样的;从第二扇区开始,就要由第一扇区中的代码来完成后续的代码加载工作。
当加载工作完成后,好像仍然没有立即执行main函数,而是打开A20,打开pe、pg,建立IDT、GDT……然后才开始执行main函数,这是什么道理?
原因是,Linux 0.11是一个32位的实时多任务的现代操作系统,main函数肯定要执行的是32位的代码。编译操作系统代码时,是有16位和32位不同的编译选项的。
如果选了16位,C语言编译出来的代码是16位模式的,结果可能是一个int型变量,只有2字节,而不是32位的4字节……这不是Linux 0.11想要的。Linux 0.11要的是32位的编译结果。只有这样才能成为32位的操作系统代码。这样的代码才能用到32位总线(打开A20后的总线),才能用到保护模式和分页,才能成为32位的实时多任务的现代操作系统。
开机时的16位实模式与main函数执行需要的32位保护模式之间有很大的差距,这个差距谁来填补? head.s做的就是这项工作。这期间,head程序打开A20,打开pe、pg,废弃旧的、16位的中断响应机制,建立新的32位的IDT……这些工作都做完了,计算机已经处在32位的保护模式状态了,
网页链接
c语言的副作用和序列点
在学习C语言运算符和表达式时,大家对优先级和结合性一定是很熟悉,但是写出代码后,发现还有计算不一致的问题。大多计算不一致问题其实和c语言副作用有关,掌握C语言副作用,你一定会知道怎样编写计算确定的程序,程序也可以很好的移植。
先看几个概念:
1。引用透明:如果一个表达式(或子表达式)只计算出值而不改变环境,我们就说它是引用透明的,这种表达式早算晚算对其他计算没有影响(不改变计算的环境。当然, 它的值可能受到其他计算的影响)。比如:(a+b)*(c+d),无论先计算乘号两边都可以
2. 副作用:如果一个表达式不仅算出一个值,还修改了环境,就说这个表达式有副作用(因为它多做了额外的事)。比如:a++
那么,多个副作用之间的发生顺序是怎样的?
C 标准规定代码执行过程中的某些时刻是Sequence Point,当到达一个Sequence Point时,在此之前的Side Effect 必须全部作用完毕,在此之后的Side Effect 必须一个都没发。至于两个Sequence Point之间的多个Side Effect 哪个先发生哪个后发生则没有规定,编译器可以任意选择各Side Effect 的作用顺序。
解释一下就是,在目标的代码里,对同一元素的多次访问(内存的访问)必然通过几段独立代码完成。现代计算机的计算都在寄存器里做,顺序点的作用就是确保在某个时刻这些改变必须反应到随后对同一存储位置的访问中。
看看常见顺序点的位置:
1. 每个完整表达式结束时。完整表达式包括变量初始化表达式,表达式语句,return 语句的表达式,以及条件、循环和 switch 语句的控制表达式(for 头部有三个控制表达式)
2. 运算符 &&、||、?: 和逗号运算符的第一个运算对象计算之后;
3. 函数调用中对所有实际参数和函数名表达式(需要调用的函数也可能通过表达式描述)的求值完成之后(进入函数体之前)。
4.在一个完整的声明末尾是Sequence Point,所谓完整的声明是指这个声明不是另外一个声明的一部分。比如声明int a[10], b[20];,在a[10]末尾是Sequence Point,在b[20]末尾也是。
5.像printf 、scanf这种带转换说明的输入/ 输出库函数,在处理完每一个转换说明相关的输入/ 输出操作时是一个Sequence Point。
6.库函数bsearch和qsort在查找和排序过程中的每一步比较或移动操作之间是一个Sequence Point。
标准截图:
这样一来,老谭绿皮书上的一个错误就显而易见了,《C程序设计》第三版64页中,有这么一个表达式 a+=a-=a*a,在a的初值为12时,该表达式的结果可能是-120也可能是-264.
原因就留给各位读者了哈。事实上,如果用gcc的wall选项编译可以看到 operation 'a' may not be defined 的警告。正是基于这些原因使得不少公司的类似的笔试题都被专业人士鄙为恶趣味。
C/C++ 语言的做法完全是有意而为,其目的就是允许编译器采用任何求值顺序,使编译器在优化中可以根据需要调整实现表达式求值的指令序列,以得到效率更高的代码。像Java 那样严格规定表达式的求值顺序和效果,不仅限制了语言的实现方式,还要求更频繁的内存访问(以实现副作用),这些可能带来可观的效率损失。应该说,在这个问题上,C/C++和 Java 的选择都贯彻了它们各自的设计原则,各有所获(C/C++ 潜在的效率,Java 更清晰的程序行为)
说这么多对我们写代码的指导意义是,在两个Sequence Point之间,同一个变量的值只允许被改变一次。另外,如果在两个Sequence Point之间既要读一个变量的值又要改它的值,只有在读写顺序确定的情况下才可以这么写。举个例子,同样是只改变变量一次,同样是等号左边写,等号右边读,i = i + i 是可行的,而a[i++] = i 的结果就是未定义的。
参考文章,裘宗燕教授的C/C++语言中的表达式求值