Notes on Programming in C

notes on c programming rob pike notes on c programming rob pike Introduction Kernighan and Plauger's The Elements of Programming Style was an important and rightly influential book. But sometimes I feel its concise rules were taken as a cookbook approach to good style instead of the succinct expression of a philosophy they were meant to be. If the book claims that variable names should be chosen meaningfully, doesn't it then follow that variables whose names are small essays on their use are even better? Isn't MaximumValueUntilOverflow a better name than maxval? I don't think so. 立即下载
 from: http://www.lysator.liu.se/c/pikestyle.html
    • Notes on Programming in C

      •             February 21, 1989

Introduction

      Kernighan and Plauger's The Elements of Programming Style was an important and rightly influential book.  But sometimes I feel its concise rules were taken as a cookbook approach to good style instead of the succinct expression of a philosophy they were meant to be.  If the book claims that variable names should be chosen meaningfully, doesn't it then follow that variables whose names are small essays on their use are even better?  Isn't MaximumValueUntilOverflow a better name than maxval I don't think so.

      What follows is a set of short essays that collectively encourage a philosophy of clarity in programming rather than giving hard rules.  I don't expect you to agree with all of them, because they are opinion and opinions change with the times.  But they've been accumulating in my head, if not on paper until now, for a long time, and are based on a lot of experience, so I hope they help you understand how to plan the details of a program.  (I've yet to see a good essay on how to plan the whole thing, but then that's partly what this course is about.)  If you find them idiosyncratic, fine; if you disagree with them, fine; but if they make you think about why you disagree, that's better.  Under no circumstances should you program the way I say to because I say to; program the way you think expresses best what you're trying to accomplish in the program.  And do so consistently and ruthlessly.

      Your comments are welcome.

Issues of typography

      A program is a sort of publication.  It's meant to be read by the programmer, another programmer (perhaps yourself a few days, weeks or years later), and lastly a machine.  The machine doesn't care how pretty the program is - if the program compiles, the machine's happy - but people do, and they should.  Sometimes they care too much: pretty printers mechanically produce pretty output that accentuates irrelevant detail in the program, which is as sensible as putting all the prepositions in English text in bold font.  Although many people think programs should look like the Algol 68 report (and some systems even require you to edit programs in that style), a clear program is not made any clearer by such presentation, and a bad program is only made laughable.

      Typographic conventions consistently held are important to clear presentation, of course - indentation is probably the best known and most useful example - but when the ink obscures the intent, typography has taken over.  So even if you stick with plain old typewriter like output, be conscious of typographic silliness.  Avoid decoration; for instance, keep comments brief and banner free.  Say what you want to say in the program, neatly and consistently.  Then move on.

Variable names

      Ah, variable names.  Length is not a virtue in a name; clarity of expression is A global variable rarely used may deserve a long name, maxphysaddr say.  An array index used on every line of a loop needn't be named any more elaborately thani Saying index or elementnumber is more to type (or calls upon your text editor) and obscures the details of the computation.  When the variable names are huge, it's harder to see what's going on.  This is partly a typographic issue; consider
        for(i=0 to 100)
                array[i]=0
vs.
        for(elementnumber=0 to 100)
                array[elementnumber]=0;
The problem gets worse fast with real examples.  Indices are just notation, so treat them as such.

      Pointers also require sensible notation.  np is just as mnemonic as nodepointer if you consistently use a naming convention from which np means ``node pointer'' is easily derived.  More on this in the next essay.

      As in all other aspects of readable programming, consistency is important in naming.  If you call one variablemaxphysaddr, don't call its cousin lowestaddress.

      Finally, I prefer minimum length but maximum information names, and then let the context fill in the rest.  Globals, for instance, typically have little context when they are used, so their names need to be relatively evocative.  Thus I saymaxphysaddr (not MaximumPhysicalAddress) for a global variable, but np not NodePointer for a pointer locally defined and used. This is largely a matter of taste, but taste is relevant to clarity.

      I eschew embedded capital letters in names; to my prose oriented eyes, they are too awkward to read comfortably.  They jangle like bad typography.

The use of pointers.

      C is unusual in that it allows pointers to point to anything.  Pointers are sharp tools, and like any such tool, used well they can be delightfully productive, but used badly they can do great damage (I sunk a wood chisel into my thumb a few days before writing this).  Pointers have a bad reputation in academia, because they are considered too dangerous, dirty somehow.  But I think they are powerful notation, which means they can help us express ourselves clearly.

      Consider: When you have a pointer to an object, it is a name for exactly that object and no other.  That sounds trivial, but look at the following two expressions:

        np
        node[i]
The first points to a node, the second evaluates to (say) the same node.  But the second form is an expression; it is not so simple.  To interpret it, we must know what node is, what i is, and that i and node are related by the (probably unspecified) rules of the surrounding program.  Nothing about the expression in isolation can show that i is a valid index of node, let alone the index of the element we want.  If i and j and k are all indices into the node array, it's very easy to slip up, and the compiler cannot help.  It's particularly easy to make mistakes when passing things to subroutines: a pointer is a single thing; an array and an index must be believed to belong together in the receiving subroutine.

      An expression that evaluates to an object is inherently more subtle and error prone than the address of that object. Correct use of pointers can simplify code:

        parent->link[i].type
vs.
	lp->type.
If we want the next element's type, it's
        parent->link[++i].type
or
        (++lp)->type.
i advances but the rest of the expression must stay constant; with pointers, there's only one thing to advance.

      Typographic considerations enter here, too.  Stepping through structures using pointers can be much easier to read than with expressions: less ink is needed and less effort is expended by the compiler and computer.  A related issue is that the type of the pointer affects how it can be used correctly, which allows some helpful compile time error checking that array indices cannot share.  Also, if the objects are structures, their tag fields are reminders of their type, so

             np->left
is sufficiently evocative; if an array is being indexed the array will have some well chosen name and the expression will end up longer:
             node[i].left.
Again, the extra characters become more irritating as the examples become larger.

      As a rule, if you find code containing many similar, complex expressions that evaluate to elements of a data structure, judicious use of pointers can clear things up.  Consider what

        if(goleft)
             p->left=p->right->left;
        else
             p->right=p->left->right;
would look like using a compound expression for p Sometimes it's worth a temporary variable (here p) or a macro to distill the calculation.

Procedure names

      Procedure names should reflect what they do; function names should reflect what they return Functions are used in expressions, often in things like if's, so they need to read appropriately.
        if(checksize(x))
is unhelpful because we can't deduce whether checksize returns true on error or non error; instead
        if(validsize(x))
makes the point clear and makes a future mistake in using the routine less likely.

Comments

      A delicate matter, requiring taste and judgement.  I tend to err on the side of eliminating comments, for several reasons.  First, if the code is clear, and uses good type names and variable names, it should explain itself.  Second, comments aren't checked by the compiler, so there is no guarantee they're right, especially after the code is modified.  A misleading comment can be very confusing.  Third, the issue of typography: comments clutter code.

      But I do comment sometimes.  Almost exclusively, I use them as an introduction to what follows.  Examples: explaining the use of global variables and types (the one thing I always comment in large programs); as an introduction to an unusual or critical procedure; or to mark off sections of a large computation.

      There is a famously bad comment style:

        i=i+1;           /* Add one to i */
and there are worse ways to do it:
        /**********************************
         *                                *
         *          Add one to i          *
         *                                *
         **********************************/

                       i=i+1;
Don't laugh now, wait until you see it in real life.

      Avoid cute typography in comments, avoid big blocks of comments except perhaps before vital sections like the declaration of the central data structure (comments on data are usually much more helpful than on algorithms); basically, avoid comments.  If your code needs a comment to be understood, it would be better to rewrite it so it's easier to understand.  Which brings us to

Complexity

      Most programs are too complicated - that is, more complex than they need to be to solve their problems efficiently. Why? Mostly it's because of bad design, but I will skip that issue here because it's a big one.  But programs are often complicated at the microscopic level, and that is something I can address here.

      Rule 1.  You can't tell where a program is going to spend its time.  Bottlenecks occur in surprising places, so don't try to second guess and put in a speed hack until you've proven that's where the bottleneck is.

      Rule 2.  Measure.  Don't tune for speed until you've measured, and even then don't unless one part of the codeoverwhelms the rest.

      Rule 3.  Fancy algorithms are slow when n is small, and n is usually small.  Fancy algorithms have big constants. Until you know that n is frequently going to be big, don't get fancy.  (Even if n does get big, use Rule 2 first.)   For example, binary trees are always faster than splay trees for workaday problems.

      Rule 4.  Fancy algorithms are buggier than simple ones, and they're much harder to implement.  Use simple algorithms as well as simple data structures.

      The following data structures are a complete list for almost all practical programs:

array  
linked list  
hash table  
binary tree
Of course, you must also be prepared to collect these into compound data structures.  For instance, a symbol table might be implemented as a hash table containing linked lists of arrays of characters.

      Rule 5.  Data dominates.  If you've chosen the right data structures and organized things well, the algorithms will almost always be self evident.  Data structures, not algorithms, are central to programming.  (See Brooks p. 102.)

      Rule 6.  There is no Rule 6.

Programming with data.

      Algorithms, or details of algorithms, can often be encoded compactly, efficiently and expressively as data rather than, say, as lots of if statements.  The reason is that the complexity of the job at hand, if it is due to a combination of independent details, can be encoded A classic example of this is parsing tables, which encode the grammar of a programming language in a form interpretable by a fixed, fairly simple piece of code.  Finite state machines are particularly amenable to this form of attack, but almost any program that involves the `parsing' of some abstract sort of input into a sequence of some independent `actions' can be constructed profitably as a data driven algorithm.

      Perhaps the most intriguing aspect of this kind of design is that the tables can sometimes be generated by another program - a parser generator, in the classical case. As a more earthy example, if an operating system is driven by a set of tables that connect I/O requests to the appropriate device drivers, the system may be `configured' by a program that reads a description of the particular devices connected to the machine in question and prints the corresponding tables.

      One of the reasons data driven programs are not common, at least among beginners, is the tyranny of Pascal.  Pascal, like its creator, believes firmly in the separation of code and data.  It therefore (at least in its original form) has no ability to create initialized data.  This flies in the face of the theories of Turing and von Neumann, which define the basic principles of the stored program computer.  Code and data are the same, or at least they can be.  How else can you explain how a compiler works? (Functional languages have a similar problem with I/O.)

Function pointers

      Another result of the tyranny of Pascal is that beginners don't use function pointers.  (You can't have function valued variables in Pascal.) Using function pointers to encode complexity has some interesting properties.

      Some of the complexity is passed to the routine pointed to.  The routine must obey some standard protocol - it's one of a set of routines invoked identically - but beyond that, what it does is its business alone.  The complexity isdistributed.

      There is this idea of a protocol, in that all functions used similarly must behave similarly.  This makes for easy documentation, testing, growth and even making the program run distributed over a network - the protocol can be encoded as remote procedure calls.

      I argue that clear use of function pointers is the heart of object oriented programming.  Given a set of operations you want to perform on data, and a set of data types you want to respond to those operations, the easiest way to put the program together is with a group of function pointers for each type.  This, in a nutshell, defines class and method.  The O O languages give you more of course - prettier syntax, derived types and so on - but conceptually they provide little extra.

      Combining data driven programs with function pointers leads to an astonishingly expressive way of working, a way that, in my experience, has often led to pleasant surprises. Even without a special O O language, you can get 90% of the benefit for no extra work and be more in control of the result.  I cannot recommend an implementation style more highly.  All the programs I have organized this way have survived comfortably after much development - far better than with less disciplined approaches.  Maybe that's it: the discipline it forces pays off handsomely in the long run.

Include files

      Simple rule: include files should never include include files.  If instead they state (in comments or implicitly) what files they need to have included first, the problem of deciding which files to include is pushed to the user (programmer) but in a way that's easy to handle and that, by construction, avoids multiple inclusions.  Multiple inclusions are a bane of systems programming.  It's not rare to have files included five or more times to compile a single C source file.  The Unix/usr/include/sys stuff is terrible this way.

      There's a little dance involving #ifdef's that can prevent a file being read twice, but it's usually done wrong in practice - the #ifdef's are in the file itself, not the file that includes it.  The result is often thousands of needless lines of code passing through the lexical analyzer, which is (in good compilers) the most expensive phase.

      Just follow the simple rule.

Notes On Writing Portable Programs In C(用C语言编写可移植程序的注意事项) Notes On Writing Portable Programs In C中文翻译版 立即下载

相关推荐

电子学习资料实验指导书电子线路课程设计题

电子学习资料实验指导书电子线路课程设计题

Notes on Programming in C” 阅读 (精简版)

Notes on Programming in C” 一文是 罗布·派克 (Rob Pike) 于 1989 年写的一份关于 C 语言编程的编程实践建议,包含 9 个主题的简要说明,涵盖了代码风格、程序优化、设计模式等内容。该文虽然是针对 C 语言所写,并且年代久远,但其中的很多想法对编写高质量的代码现在看来仍然具有非常好的指导意义。

小狼碎碎念的博客 465

参数估计Picard迭代在非线性常微分方程参数估计中的应用研究(Matlab代码实现)

内容概要:本文系统研究了Picard迭代法在非线性常微分方程参数估计中的应用,深入阐述了该方法的数学原理及其在参数辨识中的收敛性与稳定性优势。通过构建最小化误差的目标函数,并结合数值积分技术,采用迭代方式逐步逼近系统的真实参数值,有效解决了非线性动态系统中因缺乏解析解而难以进行精确建模的问题。文中提供了完整的Matlab代码实现,涵盖模型定义、迭代求解、参数更新与结果可视化等关键环节,增强了方法的可操作性与工程实用性。研究通过典型非线性系统案例验证了算法的有效性,展示了其在科学计算与工程建模中的良好适应性与推广潜力。; 适合人群:具备常微分方程理论、数值分析基础及Matlab编程能力,从事系统建模、参数辨识、动力学仿真等相关方向的研究生、科研人员和工程技术开发者。; 使用场景及目标:①解决实际工程中非线性微分方程模型的未知参数估计问题;②深入理解Picard迭代法在科学计算中的实现机制与数值特性;③为学术论文复现、科研项目开发或课程设计提供可运行、易调试的技术方案与代码参考。; 阅读建议:建议读者结合文中的数学推导与Matlab代码逐行分析,重点关注迭代流程、目标函数构造与数值积分的耦合实现,通过修改模型结构或噪声条件进行扩展实验,以深化对算法鲁棒性与适用边界的理解。配套资源可通过指定公众号和网盘链接获取,推荐同步学习以加速科研进程。

Notes on Programming in C” 阅读

Notes on Programming in C” 一文是 罗布·派克 (Rob Pike) 于 1989 年写的一份关于 C 语言编程的编程实践建议,包含多个主题的简要说明,这里是我关于这篇文章的阅读笔记。除了原文 “Introduction” 部分,其他的部分的行文都将包含如下三个部分:原文、简要翻译、评注

小狼碎碎念的博客 638

Unix哲学

Pike:Notes on Programming in C 羅勃·派克在他的《Notes on Programming in C》中提到了以下格言。虽然这些规则是关于程序设计的,但作为Unix哲学丝毫不为过: 规则一:你永远不会知道你的程序会在什么地方耗费时

Reason Cell 660

Notes on C programming language (0)

Why I wrote these notes on C? Before

YANG_BLOG的专栏 737

我是笨人——读Rob Pike的《Notes on C Programming

1. 你无法断定程序会在什么地方耗费运行时间。瓶颈经常出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在。 2. 估量。在你没对代码进行估量,特别是没找到最耗时的那部分之前,别去优化速度。 3. 花哨的算法在 n 很小时通常很慢,而 n 通常很小。花哨算法的常数复杂度很大。除非你确定 n 总是很大,否则不要用花哨算法(即使 n 很大,也优先考虑原则 2 )...

weixin_30580943的博客 191

K&R the C programming language——study notes

1.p7 关于打印字符 %d 按照十进制整数打印 %3d 按照十进制整数打印,至少3个字符宽,且在打印区域内右对齐 %f 按照浮点数打印 %6f  按照浮点数打印,至少6个字符宽 %.2f 按照浮点数打印,小数点后有两位小数 %6.2f  \t \n   printf("Hello,World\n"); 2.p9 符号常量 在C++

Blaze the Way of Quantitative Trading 2万+

C Programming Notes

C Programming Notes C Programming NotesC Programming NotesIntermediate C Programming Class Notes, Chapter 15 Steve Summit Chapter...

144

XYF.SHX

XYF.SHX

焊接变位机_1.rar

焊接变位机_1.rar

基于多尺度集成极限学习机回归(Matlab代码实现)

内容概要:本文详细介绍了一种基于多尺度集成极限学习机(Extreme Learning Machine, ELM)的回归方法,并提供了完整的Matlab代码实现。该方法通过构建多尺度特征表示与集成学习机制,有效提升了ELM在处理非线性、高维复杂数据时的预测精度与模型鲁棒性,特别适用于时间序列回归任务。文档不仅阐述了算法的核心原理与技术流程,还系统展示了其在风电功率预测等工程场景中的应用潜力。同时,文中附带了丰富的科研仿真案例集合,涵盖智能优化算法、深度学习、信号处理、电力系统调度等多个前沿方向,体现了多学科交叉融合的技术优势与实践价值。; 适合人群:具备一定Matlab编程能力,从事科学研究或工程应用的研究生、科研人员及工程技术开发者,尤其适合专注于机器学习、智能算法优化、新能源预测与电力系统建模等相关领域的专业人员。; 使用场景及目标:①用于风电、光伏、负荷等时间序列数据的高精度回归预测任务;②为科研工作者提供可复现的多尺度集成ELM模型代码框架,支持快速算法验证与二次开发;③满足实际工程项目中对高效建模、实时预测与智能决策的技术需求。; 阅读建议:建议读者结合所提供的Matlab代码进行动手实践,深入理解多尺度特征构造与集成策略的设计思想,同时可参考文档中其他相关算法案例进行横向比较与综合应用,以提升整体科研创新能力。

精密钟表配件叉片插针组装机 -原创设计.rar

精密钟表配件叉片插针组装机 -原创设计.rar

精密制造基于桥式三坐标的深孔同轴度测量:半导体流体控制阀PTFE阀体形位公差定量检测方案

内容概要:本文针对高精密制造中深孔内部特征难以精确定量测量的技术瓶颈,提出了一套基于中图仪器Mars Classic 10128桥式三坐标测量机的解决方案,重点解决长径比大于5:1的PTFE四氟阀体深孔同轴度与圆柱度测量难题。通过采用自主研发的深孔加长测针组件与CP500S扫描测头,克服了传统接触式测针刚性不足和光学设备视场遮挡的问题,实现了对深孔内部形貌的连续扫描与三维拓扑建模,输出精度可达≤0.025mm,建立了可追溯、可量化的测量标准流程。; 适合人群:从事半导体流体控制阀、高精密PTFE阀体等零部件设计、制造与质量检测的工程技术人员及测试管理人员,具备一定几何公差与三坐标测量基础知识的专业人员; 使用场景及目标:①解决“影像仪测不到深孔内部”或“深孔长径比大于5怎么测”的实际工程问题;②实现对深孔同轴度、圆柱度等形位公差的定量评价,替代传统通止规定性判断;③为工艺优化、刀具补偿和质量追溯提供精准数据支持; 阅读建议:此资源作为技术应用指南,兼具设备选型参考与测量方法指导价值,建议结合实际检测案例对照操作,并联系中图仪器技术中心获取实测验证支持。

上一篇: C/C++中的序列点(详解)
下一篇: Berkeley DB 以及 DB_SECONDARY_BAD: Secondary index inconsistent with primary 问题
sprwig
博客等级 码龄18年 4粉丝 13原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值