home *** CD-ROM | disk | FTP | other *** search
/ The C Users' Group Library 1994 August / wc-cdrom-cusersgrouplibrary-1994-08.iso / vol_100 / 137_01 / sep84col.ddj < prev    next >
Text File  |  1979-12-31  |  21KB  |  676 lines

  1. .pl 61
  2. .po 4
  3. ..
  4. .. this article was prepared for the C/Unix Programmer's Notebook
  5. .. Copyright 1984 Pyramid Systems, Inc.
  6. ..
  7. .. by A. Skjellum
  8. ..
  9. .he "C/Unix Programmer's Notebook"   for September, 1984 DDJ. 
  10.  
  11.  
  12.                                Introduction
  13.  
  14. .. okay
  15.     Past  columns have been devoted to topics about C and Unix as  they 
  16.  
  17. exist in the real world.   In this column,  I depart from this precedent by 
  18.  
  19. discussing  some  proposals for increasing the power and flexibility of  C.  
  20.  
  21. We  work in an evolving field where change  is  inevitable.   Consequently, 
  22.  
  23. many languages go through regular upgrades and improvements (eg Fortran IV, 
  24.  
  25. Fortran  66,  and Fortran 77).   C has been more immune than other  popular 
  26.  
  27. languages.   The  lack  of upgrades is probably due mainly to C's  lack  of 
  28.  
  29. intrinsic functions,  but clearly points to the basic elegance and power of 
  30.  
  31. C.  
  32.  
  33. .. okay 
  34.     I  am  aware of only a few upgrades beyond the language  definition 
  35.  
  36. specified in The C Programming Language (K&R).  These are enumerated  types 
  37.  
  38. and structure assignment.  Both of these features were introduced with Unix 
  39.  
  40. version 7 and are detailed in Unix v7 documentation.   Structure assignment 
  41.  
  42. is  useful  in what follows,  but type enumeration will not  be  mentioned.  
  43.  
  44. Therefore, while we base our definition on the Unix 7 version of C, this is 
  45.  
  46. not  likely  to  confuse those who have access only to  The  C  Programming 
  47.  
  48. Language.
  49.  
  50. .. okay
  51.     Purists  may argue that I have no business recommending changes  or 
  52.  
  53. upgrades  to  C.   Others  may argue that many of the  suggestions  can  be 
  54.  
  55. implemented via compiler preprocessors or by function calls and need not be 
  56.  
  57. part  of  the  language.   (This  second point is discussed  later  in  the 
  58.  
  59. column.)  In order to head off the critisism that I am 'tampering' with the 
  60.  
  61. C language, I offer my recommendations as a new language grammar based on C 
  62.  
  63. but  called 'X.' The letter X was chosen to denote language  extensibility, 
  64.  
  65. which is the main point of the following proposals.
  66.  
  67.                           Language Extensibility
  68.  
  69. .. okay
  70.     Most  languages allow user defined functions,  subroutines and many 
  71.  
  72. newer languages allow user defined data types.   Extensible languages  like 
  73.  
  74. Forth  and APL allow functions,  operators,  and data types  to be added to 
  75.  
  76. the programming environment in a way that makes them equivalent in  stature 
  77.  
  78. to  predefined  operations.   While  C retains  tremendous  flexibility  by 
  79.  
  80. excluding  intrinsic functions,  it does not allow user defined types to be 
  81.  
  82. treated  as easily as ints,  longs or  floats.   Specifically,  one  cannot 
  83.  
  84. extend  the definitions of operators such as addition or multiplication  to 
  85.  
  86. new  data types created with typedef.   This means that function calls must 
  87.  
  88. be used,  which while a completely viable approach,  lacks elegance.   This 
  89.  
  90. concept is illustrated in the following example.
  91.  
  92.  
  93. .. okay
  94.     As part of a program,  I need to define a data type called  COMPLEX 
  95.  
  96. which  will  function like Fortran's complex data type.  This data type  is 
  97.  
  98. used  for  handling  complex  numbers of the form A + iB  where  i  is  the 
  99.  
  100. imaginary  unit and A and B are real numbers.  This might be done with  the 
  101.  
  102. following definition:
  103.  
  104. .cp 6
  105.         typedef struct  /* complex number type definition */
  106.         {
  107.             double _creal;    /* real part */
  108.             double _cimag;  /* imaginary part */
  109.  
  110.         } COMPLEX;
  111.  
  112. .. okay
  113.     I  will work with several variables of type COMPLEX (eg  alpha  and 
  114.  
  115. beta) which are defined as follows:
  116.  
  117.         COMPLEX alpha, beta;   /* alpha and beta are complex #'s */
  118.  
  119. Up  to this point,  we have treated the complex data type  equivalently  to 
  120.  
  121. built-in types.  We can also work with pointers to or arrays of COMPLEX, so 
  122.  
  123. there  is  no  deficiency along these  lines.   However,  to  assign,  add, 
  124.  
  125. multiply, or subtract these COMPLEX variables, subroutines would have to be 
  126.  
  127. invented.  Subroutines for two representative operations are illustrated in 
  128.  
  129. Figure I.
  130.  
  131. .pa
  132.                       ---------- FIGURE I. ----------
  133.  
  134.     Assignment:  alpha = A + iB; /* pseudo code */
  135.  
  136.     Function:    
  137.         calling sequence    (K&R C):     cassign(&alpha,A,B);
  138.         calling sequence    (Unix 7 C):  alpha = cassign(A,B);
  139.  
  140.         function definition (K&R C):
  141.  
  142.             cassign(comp,a,b)
  143.             COMPLEX *comp;
  144.             double a,b;
  145.             {
  146.                 comp->_creal = a;
  147.                 comp->_cimag = b;
  148.             }
  149.  
  150.         function definition (Unix 7 C):
  151.  
  152.             COMPLEX cassign(a,b)
  153.             double a,b;
  154.             {
  155.                 COMPLEX temp;   /* temporary variable */
  156.  
  157.                 temp._creal = a;
  158.                 temp._cimag = b;
  159.                 return(temp);   /* return structure */
  160.             }
  161.  
  162.  
  163.  
  164.     Addition:  gamma = alpha + beta; /* pseudo code */
  165.  
  166.     Function:    
  167.         calling sequence    (K&R C):     cadd(&gamma,&alpha,&beta);
  168.         calling sequence    (Unix 7 C):  gamma = cadd(alpha,beta);
  169.  
  170.         function definition (K&R C):
  171.  
  172.             cadd(gamma,alpha,beta)
  173.             COMPLEX *gamma;    /* destination */
  174.             COMPLEX *alpha; /* addend */
  175.             COMPLEX *beta;  /* augend */
  176.             {
  177.                 gamma->_creal = alpha->_creal + beta->_creal;
  178.                 gamma->_cimag = alpha->_cimag + beta->_cimag; 
  179.             }
  180.  
  181. .cp 10
  182.         function definition (Unix 7 C):
  183.  
  184.             COMPLEX cadd(alpha,beta)
  185.             COMPLEX alpha,beta; /* addend, augend */
  186.             {
  187.                 COMPLEX temp; /* temporary */
  188.  
  189.                 temp._creal = alpha._creal + beta._creal;
  190.                 temp._cimag = alpha._cimag + beta._cimag;
  191.  
  192.                 return(temp);
  193.             }
  194.  
  195.  
  196.                     ---------- END FIGURE I. ----------
  197.  
  198.  
  199. .. okay
  200.     The pseudo code presented with the subroutines in Figure I.  is the 
  201.  
  202. most convenient way to specify the operations desired.   If the data  types 
  203.  
  204. had been intrinsic, we could have used similar real C statements in lieu of 
  205.  
  206. subroutines.   To utilize '+', '*' or other operators with the COMPLEX data 
  207.  
  208. type  we must introduce a mechanism for defining these operations.
  209.  
  210.                                  Operators
  211.  
  212. .. okay
  213.     How  could  we  specify new operations?  For example how  would  we 
  214.  
  215. define  addition  for  the  complex  data  type?   The  following  type  of 
  216.  
  217. definition could be used to extend addition to the COMPLEX type: 
  218.  
  219.         COMPLEX oper `+`(alpha,beta)    /* X grammar */
  220.         COMPLEX alpha,beta;
  221.         {
  222.             COMPLEX __temp; /* temporary */
  223.             __temp._creal = alpha._creal + beta._creal;
  224.             __temp._cimag = alpha._cimag + beta._cimag;
  225.  
  226.             return(__temp); /* return result */ 
  227.         }
  228.  
  229. The  keyword oper is new:  oper indicates that the following definition  is 
  230.  
  231. for  an operator.   The return keyword used in function calls also  appears 
  232.  
  233. with a similar meaning.      Since COMPLEX preceeds oper,  this defines  an 
  234.  
  235. operation  over  the  COMPLEX data type.   Since there  are  two  arguments 
  236.  
  237. (alpha,  beta), the operator is binary.  Finally, note that the '+' sign is 
  238.  
  239. enclosed  in graven accents.   Quoting by graven accents is chosen as a way 
  240.  
  241. to distinguish operator names.   We will see that quotation will not always 
  242.  
  243. be needed.
  244.  
  245. .. okay
  246.     To  use  this  new operator (and assuming that '='  had  also  been 
  247.  
  248. defined,) the following statement could be used:
  249.  
  250.         gamma = alpha + beta;    /* add complex numbers */
  251.  
  252. Note  that  we  have  omitted the graven accents.   Since the  '+'  can  be 
  253.  
  254. distinguished from keywords or identifiers in this context, quoting  is not 
  255.  
  256. required.  The  operator definition specified above gives the X compiler  a 
  257.  
  258. means to evaluate the addition request specified in the example  statement.  
  259.  
  260. The  parser would break this statement down until it could pass an argument 
  261.  
  262. garnered from the left and right of the addition operator,  much as it does 
  263.  
  264. with  intrinsic  operators  and data types.   Whether  this  results  in  a 
  265.  
  266. subroutine   call   or  in-line  code  would  depend  on   the   compiler's 
  267.  
  268. implementation.
  269.  
  270.                              More on Operators
  271.  
  272. .. okay
  273.     Operators  turn out to be a very powerful and useful  concept.   We 
  274.  
  275. needn't  limit  ourselves to defining standard operations  for  new  types.  
  276.  
  277. There  is nothing to stop the definition of arbitrary operators.   A  crude 
  278.  
  279. facility  already  exists  for  this in C  via  the  parameterized  #define    
  280.  
  281. statement.  However, the above facility is more general and more consistent 
  282.  
  283. with the syntax of C than the preprocessor #define approach.   To encompass 
  284.  
  285. the  generation of inline code as provided by #define,  we would also offer 
  286.  
  287. the inline adjective, which could be used as follows:
  288.  
  289.         COMPLEX inline oper `-`(alpha,beta) /* subtraction inline */
  290.         ...
  291.  
  292. This  keyword  would  instruct the compiler to  generate  inline  code  (as 
  293.  
  294. opposed to a subroutine call) whenever possible.   It's use is analogous to 
  295.  
  296. the use of the register adjective:  the compiler complies when feasible and 
  297.  
  298. silently ignores the request when it cannot comply.
  299.  
  300. .. okay
  301.     In  some  cases,  C definitions can be shortened when no  ambiguity 
  302.  
  303. exists  (eg 'unsigned' instead of  'unsigned  int').   Therefore,  'inline' 
  304.  
  305. would  replace 'inline oper' in actual  practice.   Furthermore,  operators 
  306.  
  307. would by default work on and return integers, as functions do by default.
  308.  
  309.                          Other Users for Operators
  310.  
  311. .. okay
  312.     In  my view,  operators would be used not only to  define  existing 
  313.  
  314. operations  over new data types,  but also for specifying other  operations 
  315.  
  316. over  new  as  well  as existing data types.   These  new  operators  would 
  317.  
  318. normally  have alphanumeric names and would thus require quoting in  graven 
  319.  
  320. accents  when  they  appear in expressions.   For example,  we  define  the 
  321.  
  322. operation of NAND (negated and) for integers as follows (no graven  accents 
  323.  
  324. are required in the definition but are required in the below invocation): 
  325.  
  326.         int oper nand(a,b)
  327.         int a,b;
  328.         {
  329.             return(~(a & b));
  330.         }
  331.  
  332. To use this in an actual expression we would have to quote the nand:
  333.  
  334.         c = a `nand` b;
  335.  
  336.                             Operator Hierarchy
  337.  
  338. .. okay
  339.     C already has a built-in hierarchy for known operations.   The most 
  340.  
  341. reasonable  approach is to give user-defined operators the lowest priority.  
  342.  
  343. This might require more parentheses, but seems logical.  
  344.  
  345.                            Pointers to Operators
  346.  
  347. .. okay
  348.     C  provides  the facility to use pointers to functions.   It  could 
  349.  
  350. potentially  prove  useful  to  have pointers  to  operators  as  well.   A 
  351.  
  352. function's  address is specified by its name without trailing  parentheses.  
  353.  
  354. Unfortunately,  operator  names  are  used  in this  way  to  indicate  the 
  355.  
  356. operation they represent.   In order to remove the ambiguity in  requesting 
  357.  
  358. the pointer, the operator name could be parenthesized (eg (+) or (`nand`)).  
  359.  
  360. .. okay
  361.     Using  pointers  to operators implies that defined operations  must 
  362.  
  363. have  subroutines associated with them.   Thus truly inline operators could 
  364.  
  365. have no pointers associated with them.
  366.  
  367.                    Dichotomy of Operators and Functions
  368.  
  369. .. okay
  370.     Functions and operators are almost the same  thing.   However,  the 
  371.  
  372. compiler  must  know if an operator is binary  or  unary.   Therefore,  its 
  373.  
  374. definition must be available before use.  On the other hand, arguments to C 
  375.  
  376. functions  are not checked for number or type.    Therefore,  we choose  to 
  377.  
  378. keep operators and functions separate, although there is nothing to prevent 
  379.  
  380. operators using function calls.
  381.  
  382. .. okay
  383.     In  order to avoid lexical conflicts,  operator and function  names 
  384.  
  385. would  have  to be different.   This is also desirable from  a  programming 
  386.  
  387. viewpoint, in order to avoid confusion and errors.
  388.  
  389.                               Other Proposals
  390.  
  391. .. okay
  392.     With the addition of operators,  the X grammar provides a much more 
  393.  
  394. consistent  programming environment than standard C.   However,  there  are 
  395.  
  396. some  other  points  with deserve consideration.   The first  of  these  is 
  397.  
  398. providing  a  means  to  handle  subroutines  with  a  variable  number  of 
  399.  
  400. arguments.  This is considered first.
  401.  
  402.     Since  C makes no assumptions about its function library,  the user 
  403.  
  404. is free to write his own,  should the standard functions prove  inadequate.  
  405.  
  406. However,  the user cannot properly handle functions with variable number of 
  407.  
  408. arguments,  as must be done by printf(),  scanf() and their relatives.   We 
  409.  
  410. solve  this  problem by introducing a typing adjective called vec which  is 
  411.  
  412. short  for vector.   This adjective is used to indicate the the  number  of 
  413.  
  414. arguments  to  the  function is  variable.   For  example,  the  ficticious 
  415.  
  416. function  my_printf()  which  allows  variable arguments  (and  returns  an 
  417.  
  418. integer) would be defined as follows:
  419.  
  420.         vec int my_printf(argcnt,argvec)
  421.         int argcnt;
  422.         char *argvec[];
  423.         {
  424.             /* code goes here */
  425.         }
  426.  
  427. .. okay
  428. A  function declared with vec always has  two  arguments:  argcnt,  argvec.  
  429.  
  430. These variables are analogous to main()'s (argc,argv) pair.   Before use, a 
  431.  
  432. definition of the form:
  433.  
  434.         vec my_printf();
  435.  
  436. would  be  included  in each file where  my_printf()  is  referenced.  This 
  437.  
  438. definition  causes  command line arguments to be  processed  normally:  the 
  439.  
  440. rightmost argument is pushed (placed on the stack) first,  and the leftmost 
  441.  
  442. last when code is generated.   However, the two additional arguments argcnt 
  443.  
  444. and  argvec  are  also stacked.   The argvec variable points to  the  stack 
  445.  
  446. location where the first real argument is located.  Since normal stacks are 
  447.  
  448. push-down,  this should provide the arguments in the correct order.  argcnt 
  449.  
  450. contains  the  number of arguments plus one to account  for  argvec.   This 
  451.  
  452. makes it completely analogous to argc.   argvec always contains an address, 
  453.  
  454. but this is not very useful if no arguments were specified in the  function 
  455.  
  456. call.
  457.  
  458. .. okay
  459.     To  illustrate  the  stacking mechanism,  imagine  that  we  invoke 
  460.  
  461. my_printf() as follows:
  462.  
  463.         my_printf(arg1,arg2,arg3,arg4,arg5);
  464.  
  465. For  this  specific call,  the stacking arrangment (excluding  any  special 
  466.  
  467. register saves) would look as specified in Figure II.   Note that argcnt is 
  468.  
  469. six (five arguments) for this case, as described above.
  470.  
  471. .pa
  472.                      ---------- Figure II. ----------
  473.  
  474.             Memory Layout for a variable argument function call 
  475.  
  476.                             -------------------
  477.                             |   Low memory    |
  478.                             -------------------
  479.                             |       ...       |
  480.                             -------------------
  481.                             |   argcnt = 6    |
  482.                             -------------------
  483.                             |   argvec = ADDR |
  484.                             -------------------
  485.                       ADDR: |      arg1       |
  486.                             -------------------
  487.                             |      arg2       |
  488.                             -------------------
  489.                             |      arg3       |
  490.                             -------------------
  491.                             |      arg4       |
  492.                             -------------------
  493.                             |      arg5       |
  494.                             -------------------
  495.                             |       ...       |
  496.                             -------------------
  497.                             |   High memory   |
  498.                             -------------------
  499.       
  500.                    ---------- End Figure II. ----------
  501.  
  502.  
  503. .. okay
  504.     It might be worthwhile to have variable argument calls, even if the 
  505.  
  506. function were not declared as using this calling convention. To allow this, 
  507.  
  508. we  introduce  the  ellispis (...) concept into the  argument  string.   If 
  509.  
  510. my_printf()  were  not declared as vec,  we could force  variable  argument 
  511.  
  512. format as follows:
  513.  
  514.         my_printf(arg1,arg2,arg3,arg4,arg5...);
  515.  
  516. Always  including  the  ellipsis mark for this variety  of  call  seems  to 
  517.  
  518. improve  readability,  but  is not required in order that compatibility  is 
  519.  
  520. kept with current C usage.
  521.  
  522.                               Fixed Arguments
  523.  
  524. .. okay 
  525.     The  argvec variable always points to the first variable  specified 
  526.  
  527. on  the  command  line.   However,  the  function  definition  could  still 
  528.  
  529. explicitly  declare  a  finite number of arguments which  it  may  wish  to 
  530.  
  531. examine more directly.   For example,  if the first argument of my_printf() 
  532.  
  533. were a control string, we could declare my_printf() as follows:
  534.  
  535.         vec int my_printf(argcnt,argvec,control_string);
  536.         int argcnt;
  537.         char **argvec;
  538.         char *control_string;
  539.  
  540. Notice that contents of control_string would be meaningless if argcnt  were 
  541.  
  542. less than two.  
  543.  
  544. .. okay
  545.      One final note about variable argument control is that it enhances 
  546.  
  547. a  function's  ability  to  detect  incorrect  input.   With  reference  to 
  548.  
  549. printf(),  Kernighan and Ritchie state:  "A warning:  printf uses its first 
  550.  
  551. argument to decide how many arguments follow and what their types are.   It 
  552.  
  553. will  get confused,  if there are not enough arguments or if they  are  the 
  554.  
  555. wrong  type."  If implemented with the vec arrangement,  printf() could  at 
  556.  
  557. least  know if it has been given the right number of arguments.   It  still 
  558.  
  559. would not know if they were of the correct types.
  560.  
  561.                      Variable length automatic arrays
  562.  
  563. .. okay
  564.     Another  element  of  the  X  grammar is  the  ability  to  declare 
  565.  
  566. automatic arrays which possess variable length.   Since stack displacements 
  567.  
  568. are  computed at each entry to a block,  this only forces a  computed  size 
  569.  
  570. allocation.   At worst, a memory allocation mechanism must be tied into the 
  571.  
  572. compiler.   This  latter restriction can be serious if C is used in a  very 
  573.  
  574. low  level environment,  such as in operating system development.   So that 
  575.  
  576. the use of this feature can be seen readily,  we require the use of the var 
  577.  
  578. adjective  in conjunction with such definitions.   For  most  purposes,  it 
  579.  
  580. offers  a  welcome enhancement.   Where it is inappropriate,  this  feature 
  581.  
  582. should be disabled via a compiler switch. 
  583.  
  584.     As  a general example,  we declare a variable length array  in  the 
  585.  
  586. following routine:
  587.  
  588.                 /* declare an array of integers one larger than argument */
  589.         array_test(length)
  590.         int length;
  591.         {
  592.             var int test[length+1]; /* declare array */
  593.             ...
  594.         }
  595.  
  596.                           A new looping structure
  597.  
  598. .. okay
  599.     Many  loops  are  unconditional  with breaks  generated  only  from 
  600.  
  601. within.   Therefore  it  is often useful to have an  unconditional  looping 
  602.  
  603. command.   This  avoids  a  lot of  'while(1)'  sequences.  This  could  be 
  604.  
  605. implemented as follows:
  606.  
  607. .cp 7
  608.         loop
  609.         {
  610.             ... code ...
  611.         }
  612.  
  613. replaces
  614.  
  615.         while(1)
  616.         {
  617.             ... code ...
  618.         }
  619.  
  620.  
  621.                     Preprocessors and related comments
  622.  
  623. .. okay
  624.     Preprocessors  could be used to implement several of the X features 
  625.  
  626. mentioned above.   The statements and expressions would be expanded by  the 
  627.  
  628. preprocessor  into  standard function calls.   The preprocessor would  also 
  629.  
  630. provide  subroutines from definitions,  as needed.   New data  types  could 
  631.  
  632. certainly be handled in this way.  However, changes to the C parser must be 
  633.  
  634. made  in order to handle the vec and var features.   Trivial additions such 
  635.  
  636. as loop can be handled with the existing C preprocessor.
  637.  
  638. .. okay
  639.     Some programmers may argue that no additions are needed since  most 
  640.  
  641. of  the features outlined above can all be achieved through function calls.  
  642.  
  643. In my view, the X grammar makes C more (and not less) consistent because it 
  644.  
  645. allows both intrinsic and user-defined types to be handled in similarly. It 
  646.  
  647. also allows greater portability by defining a means through which  variable 
  648.  
  649. argument functions can be handled uniformly.  In summation, it turns C into 
  650.  
  651. an extensible language while adding only a few new keywords.
  652.  
  653.                                 Conclusion
  654.  
  655. .. okay
  656.     In  this column,  I have suggested an enhanced C grammar which  was 
  657.  
  658. denoted  X to indicate extensibility.   It is the (Unix 7) C language  with 
  659.  
  660. enhancements   designed  to  allow  the  incorporation  of   user-specified 
  661.  
  662. operators into programs.  This should provide  more flexible and consistent 
  663.  
  664. reference to user-defined data types.   Also mentioned were variable length 
  665.  
  666. automatic  arrays  (var)  and a mechanism for  allowing  variable  argument 
  667.  
  668. functions (vec).   Finally, the use of preprocessors for implementing these 
  669.  
  670. ideas was mentioned.
  671.  
  672.     I  look forward to any other ideas about X or enhanced C which  may 
  673.  
  674. be forthcoming from our readers.
  675.  
  676.