summaryrefslogtreecommitdiff
path: root/reference/C/CONTRIB/SAWTELL/c-lesson.8
blob: 8e2bff91f9289cfa519e882e53b2a21a35a8463d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502

                                     Lesson 7.

                             De-bugging Strategies.

      >>>>>>>> Proper Preparation Prevents Piss-Poor Performance. <<<<<<<<

  This lesson is really a essay about how to go about writing programs.

  I know that by far the best way to greatly reduce the amount of effort
required to get a program going properly is to avoid making mistakes in the
first palace! Now this might seem to be stating the absolute obvious, and it
is but after looking at many programs it would seem that there is a very
definite need to say it.

  So how does one go about reducing the probability of making mistakes?

  There are many strategies, and over the years I have evolved my own set.
  I have found that some of the most important are:

  1) Document what you are going to do before yes BEFORE you write any code.
     Set up the source files for the section of the program you are going to
     write and put some lines of explanation as to what you intend to do in
     this file. Be as precise as you can, but don't go into the detail of
     explaining in English, or your First Language, exactly what every
     statement does.

  2) Make sure that you keep each file as small as is sensible. Some program
     authors say that one should put only one function in a file. It's my
     personal opinion that this is going a little bit over the top, but
     certainly you should not have more than one logical activity in a source
     file. It's easier to find a needle in a tiny haystack than in a big one!

  3) Always use names for the objects in your program which are fully
     descriptive, or at the very least are meaningful nmemonics. Put yourself
     in the position of some poor soul who - a couple of years later, after you
     have long finished with the project, and left the country - has been given
     the task of adding a small feature to your exquisite program. Now in the
     rush to get your masterpiece finished you decided to use variable names
     like "a4" and "isb51" simply so that you can get the line typed a
     fraction of a second faster than if you used something like
     "customer_address[POST_CODE]" and "input_status_block[LOW_FUEL_TANK_#3].
     The difference in ease of understanding is obvious, isn't it? However
     judging by some programs which I have seen published in both magazines and
     in the public domain program sources, the point has still to be made.

  4) ALWAYS take great care with the layout of your code.
     It's my opinion that the opening brace of ALL program structures should
     be on a new line. Also if you put them in the leftmost column for structs,
     enums, and initialised tables, as well as functions, then the
     'find function' keystrokes ( "[[" and "]]" ) in vi will find them as well
     as the functions themselves. Make sure you have the "showmatch" facility
     in vi turned on. ( And watch the cursor jump when you enter the
     right hand brace, bracket, or parenthesis. )

  5) Try as hard as you can to have as few global variables as possible.
     Some people say never have any globals. This is perhaps a bit too
     severe but global variables are a clearly documented source of
     programming errors. If it's impossible to perform a logical activity
     in an efficient way without having a global or two, then confine
     the scope of the globals to just the one file by marking the defining
     declaration "static". This stops the compiler producing a symbol which
     the linking loader will make available to all the files in your source.

  6) Never EVER put 'magic numbers' in you source code. Always define constants
     in a header file with #define lines or enum statements.

     Here is an example:-


/* ----------------------------------------- */

#include <stdio.h>

enum status_input_names
{
  radiator_temperature,
  oil_temperature,
  fuel_pressure,
  energy_output,
  revolutions_per_minute
  };

char *stats[] =
{
  "radiator_temperature",
  "oil_temperature",
  "fuel_pressure",
  "energy_output",
  "revolutions_per_minute"
  };

#define NUMBER_OF_INPUTS ( sizeof ( stats ) / sizeof ( stats[0]))

main()
{
  enum status_input_names name;

  printf ( "Number of Inputs is: %d\n", NUMBER_OF_INPUTS );
  for ( name = radiator_temperature; name < NUMBER_OF_INPUTS; name++)
  {
    printf ( "\n%s", stats[ name ] );
    }
  printf ( "\n\n" );
  }

/* ----------------------------------------- */

  Note that as a side effect we have available the meaningful symbols
  radiator_temperature etc. as indices into the array of status input names
  and the symbol NUMBER_OF_INPUTS available for use as a terminator in the
  'for' loop. This is quite legal because sizeof is a pseudo-function and the
  value is evaluated at the time of compilation and not when the program is
  executed. This means that the result of the division in the macro is
  calculated at the time of compilation and this result is used as a literal
  in the 'for' loop. No division takes place each time the loop is executed.

  To illustrate the point I would like to tell you a little story which is
  fictitious, but which has a ring of truth about it.
  Your employer has just landed what seems to be a lucrative contract with
  an inventor of a completely new type of engine. We are assured that after
  initial proving trials one of the larger Japanese motor manufactures is
  going to come across with umpteen millions to complete the development of
  the design. You are told to write a program which has to be a simple and
  straightforward exercise in order to do the job as cheaply as possible.
  Now, the customer - a some-what impulsive type - realises that his
  engine is not being monitored closely enough when it starts to rapidly
  dis-assemble itself under high speed and heavy load. You have to add a
  few extra parameters to the monitoring program by yesterday morning!
  You just add the extra parameters into the enumand the array of pointers
  to the character strings. So:

enum status_input_names
{ radiator_temperature,
  radiator_pressure,
  fuel_temperature,
  fuel_pressure,
  oil_temperature,
  oil_pressure,
  exhaust_manifold_temperature
  };

  Let's continue the story about the Japanese purchase. Mr. Honda ( jun ) has
  come across with the money and the result is that you are now a team leader
  in the software section of Honda Software ( YourCountry ) Ltd. The project of
  which you are now leader is to completely rewrite your monitoring program and
  add a whole lot of extra channels as well as to make the printouts much more
  readable so that your cheap, cheerful, and aesthetic-free program can be sold
  as the "Ultimate Engine Monitoring Package" from the now world famous Honda
  Real-time Software Systems. You set to work, Honda et. al. imagine that there
  is going to be a complete redesign of the software at a cost of many million
  Yen. You being an ingenious type have written the code so that it is easy to
  enhance.

  The new features required are that the printouts have to be printed with the
  units of measure appended to the values which have to scaled and processed so
  that the number printed is a real physical value instead of the previous
  arrangement where the raw transducer output was just dumped onto a screen.

  What do you have to do?

  Thinking along the line of "Get the Data arranged correctly first".
  You take you old code and expand it so that all the items of information
  required for each channel are collected into a struct.

enum status_input_names
{
  radiator_temperature,
  radiator_pressure,
  fuel_temperature,
  fuel_pressure,
  oil_temperature,
  oil_pressure,
  exhaust_manifold_temperature,
  power_output,
  torque
  };

typedef struct channel
{
  char *name;                    /* Channel Name to be displayed on screen. */
  int nx;                        /* position of name on screen x co-ordinate.
*/
  int ny;                        /* ditto for y */
  int unit_of_measure;           /* index into units of measure array */
  char value;                    /* raw datum value from 8 bit ADC */
  char lower_limit;              /* For alarms. */
  char upper_limit;
  float processed_value;         /* The number to go on screen. */
  float offset;
  float scale_factor;
  int vx;                        /* Position of value on screen. */
  int vy;
  }CHANNEL;

enum units_of_measure { kPa, degC, kW, rpm, Volts, Amps, Newtons };

char *units { "kPa", "degC", "kW", "rpm", "Volts", "Amps", "Newtons" };

CHANNEL data [] =
{
  { "radiator temperature",
  { "radiator pressure",
  { "fuel temperature",
  { "fuel pressure",
  { "oil temperature",
  { "oil pressure",
  { "exhaust manifold temperature",
  { "power output",
  { "torque",
  };

#define NUMBER_OF_INPUTS sizeof (data ) / sizeof ( data[0] )

Now the lesson preparation is to find the single little bug in the above
program fragment, to finish the initialisation of the data array of type
CHANNEL and to have a bit of a crack at creating a screen layout
program to display its contents. Hint: Use printf();
( Leave all the values which originate from the real world as zero. )


  Here are some more tips for young players.

  1) Don't get confused between the logical equality operator,

     ==

     and the assignment to a variable operator.

     =

     This is probably the most frequent mistake made by 'C' beginners, and
     has the great disadvantage that, under most circumstances, the compiler
     will quite happily accept your mistake.

  2) Make sure that you are aware of the difference between the logical
     and bit operators.

     &&         This is the logical AND function.
     ||         This is the logical OR function.
                The result is ALWAYS either a 0 or a 1.

     &          This is the bitwise AND function used for masks etc.
                The result is expressed in all the bits of the word.

  3) Similarly to 2 be aware of the difference between the logical
     complementation and the bitwise one's complement operators.

     !          This is the logical NOT operator.
     ~          This is the bitwise ones complement op.

     Some further explanation is required. In deference to machine efficiency a
     LOGICAL variable is said to be true when it is non-zero. So let's set a
     variable to be TRUE.

     00000000000000000000000000000001  A word representing TRUE.
                                       Now let's do a logical NOT  !.
     00000000000000000000000000000000  There is a all zero word, a FALSE.

     00000000000000000000000000000001  That word again. TRUE.
                                       Now for a bitwise complement  ~.
     11111111111111111111111111111110  Now look we've got a word which is
                                       non-zero, still TRUE.

                                       Is this what you intended?

  4) It is very easy to fall into the hole of getting the
     '{' & '}'; '[' & ']'; '(' & ')'; symbol pairs all messed up and the
     computer thinks that the block structure is quite different from that
     which you intend. Make sure that you use an editor which tells you the
     matching symbol. The UNIX editor vi does this provided that you turn
     on the option. Also take great care with your layout so that the block
     structure is absolutely obvious, and whatever style you choose do take
     care to stick by it throughout the whole of the project.
     A personal layout paradigm is like this:

  Example 1.

function_type function_name ( a, b )
type a;
type b;
{
  type variable_one, variable_two;

  if ( logical_expression )
  {
    variable_one = A_DEFINED_CONSTANT;
    if ( !return_value = some_function_or_other ( a,
                                                  variable_one,
                                                  &variable_two
                                                  )
         )
    {
      error ( "function_name" );
      exit ( FAILURE );
      }
    else
    {
      return ( return_value + variable_two );
      }
    }    /* End of "if ( logical_expression )" block */
  }    /* End of function */

  This layout is easy to do using vi with this initialisation script
  in either the environment variable EXINIT or the file ${HOME}/.exrc:-

set showmode autoindent autowrite tabstop=2 shiftwidth=2 showmatch wm=1

  Example 2.

void printUandG()
{
  char *format =
"\n\
           User is: %s\n\
          Group is: %s\n\n\
 Effective User is: %s\n\
Effective Group is: %s\n\n";

  ( void ) fprintf ( tty,
                     format,
                     passwd_p->pw_name,
                     group_p->gr_name,
                     epasswd_p->pw_name,
                     egroup_p->gr_name
                     );
  }

  Notice how it is possible to split up format statements with a '\' as
  the last character on the line, and that it is convenient to arrange
  for a nice output format without having to count the
  field widths. Note however that when using this technique that the '\'
  character MUST be the VERY LAST one on the line. Not even a space may
  follow it!

  In summary I *ALWAYS* put the opening brace on a new line, set the tabs
  so that the indentation is just two spaces, ( use more and you very quickly
  run out of "line", especially on an eighty column screen ). If a statement
  is too long to fit on a line I break the line up with the arguments set out
  one to a line and I then the indentation rule to the parentheses "()"
  as well. Sample immediately above. Probably as a hang-over from a particular
  pretty printing program which reset the indentation position after the
  printing of the closing brace "}", I am in the habit of doing it as well.
  Long "if" and "for" statements get broken up in the same way. This is
  an example of it all. The fragment of code is taken from a curses oriented
  data input function.

  /*
  ** Put all the cursor positions to zero.
  */

  for ( i = 0;
        s[i].element_name != ( char *) NULL &&
        s[i].element_value != ( char *) NULL;
        i = ( s[i].dependent_function == NULL )
            ? s[i].next : s[i].dependent_next
        )
  {                              /* Note that it is the brace and NOT the    */
                                 /* "for" which moves the indentation level. */
    s[i].cursor_position = 0;
    }

  /*
  ** Go to start of list and hop over any constants.
  */

    for ( i = edit_mode = current_element = 0;
          s[i].element_value == ( char *) NULL ;
          current_element = i = s[i].next
          ) continue;                               /* Note EMPTY statement. */

  /*
  ** Loop through the elements, stopping at end of table marker,
  ** which is an element with neither a pointer to an element_name nor
  ** one to a element_value.
  */

  while ( s[i].element_name != ( char *) NULL &&
          s[i].element_value != ( char *) NULL
          )
  {
    int c;           /* Varable which holds the character from the keyboard. */

    /*
    **  Et Cetera for many lines.
    */

    }

  Note the commenting style. The lefthand comments provide a general
overview of what is happening and the righthand ones a more detailed view.
The double stars make a good marker so it is easy to separate the code and
the comments at a glance.

  The null statement.

  You should be aware that the ";" on its own is translated by the compiler
as a no-operation statement. The usefullness of this is that you can do
little things, such as counting up a list of objects, or positioning a pointer
entirely within a "for" or "while" statement. ( See example above ).
There is, as always, a flip side. It is HORRIBLY EASY to put a ";" at the
end of the line after the closing right parenthesis - after all you do just
that for function calls! The suggestion is to both mark deliberate null
statements with a comment and to use the statement "continue;". Using
the assert macro will pick up these errors at run time.

  The assert macro.

  Refer to the Programmers Reference Manual section 3X and find the
documentation on this most useful tool.

  As usual an example is by far the best wasy to explain it.

/* ----------------------------------------- */

#ident "@(#) assert-demo.c"

#include <stdio.h>
#include <assert.h>

#define TOP_ROW 10
#define TOP_COL 10

main()
{
  int row, col;

  for ( row = 1; row <= TOP_ROW; row++);
  {
    assert ( row <= TOP_ROW );
    for ( col = 1; col <= TOP_COL; col++ )
    {
      assert ( col <= TOP_COL );
      printf ( "%4d", row * col );
      }
    printf ( "\n" );
    }
  }

/* ----------------------------------------- */

  Which produces the output:-

Assertion failed:  row <= TOP_ROW , file assert-demo.c, line 15
ABORT instruction (core dumped)

  It does this because the varable "row" is incremented
to one greater than The value of TOP_ROW.

  Note two things:

  1) The sense of the logical condition. The assert is asserted
     as soon as the result of the logical condition is FALSE.
     Have a look at the file /usr/include/assert.
     Where is the ";" being used as an empty program statement?

  2) The unix operating system has dumped out an image of the executing
     program for examination using a symbolic debugger. Have a play with
     "sdb" in preparation for the lesson which deals with it in more
     detail.

  Lets remove the errant semi-colon, re-compile and re-run the program.

   1   2   3   4   5   6   7   8   9  10
   2   4   6   8  10  12  14  16  18  20
   3   6   9  12  15  18  21  24  27  30
   4   8  12  16  20  24  28  32  36  40
   5  10  15  20  25  30  35  40  45  50
   6  12  18  24  30  36  42  48  54  60
   7  14  21  28  35  42  49  56  63  70
   8  16  24  32  40  48  56  64  72  80
   9  18  27  36  45  54  63  72  81  90
  10  20  30  40  50  60  70  80  90 100

  Here's the ten times multiplication table, for you to give to to
the nearest primary-school child!

  I would agree that it is not possible to compare the value of a program
layout with a real work of fine art such as a John Constable painting or
a Michaelangelo statue, I do think a well laid out and literate example of
programming is not only much easier to read and understand, but also it
does have a certain aesthetic appeal.

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
but not modification, of this course and its use for personal
study only, provided all the copyright notices are left in the
text and are printed in full on any subsequent paper reproduction.

--
 +----------------------------------------------------------------------+
 | NAME   Christopher Sawtell                                           |
 | SMAIL  215 Ollivier's Road, Linwood, Christchurch, 8001. New Zealand.|
 | EMAIL  chris@gerty.equinox.gen.nz                                    |
 | PHONE  +64-3-389-3200   ( gmt +13 - your discretion is requested )   |
 +----------------------------------------------------------------------+