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

  1. .pl 61
  2. .po 0
  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. .. Listing I.  -- lsup.c
  10. .. Listing II. -- lsup.h
  11. .. Listing III.-- _lsup.h
  12. .. Listing IV. -- llsup.asm
  13. .. Listing V.  -- llint.asm
  14. .. Listing VI. -- env.asm
  15. .. 
  16. .he "C/Unix Programmer's Notebook"   for June, 1984 DDJ. 
  17.  
  18.                                Introduction
  19.  
  20.      In  previous columns,  I have alluded to 8086 family C compilers which 
  21.  
  22. support large memory models.  Before discussing some of the compilers which 
  23.  
  24. provide such features,  I thought it worthwhile to discuss the memory model 
  25.  
  26. concepts of the 8086 and how this impacts C compilers implemented for  this 
  27.  
  28. microprocessor  family.   I  will discuss the advantages and  drawbacks  of 
  29.  
  30. several memory models used by existing C compilers.  Code will be presented 
  31.  
  32. to help overcome some of the limitations of small memory model compilers.
  33.  
  34.      For  those  readers  who are not interested in the details of  8086  C 
  35.  
  36. compilers,  large  memory models,  or long pointers,  there is  still  some 
  37.  
  38. interesting material in this column.   Specifically, several routines which 
  39.  
  40. are  included  here illustrate real-life code to interface C  and  assembly 
  41.  
  42. language.   Most  compiler  manuals are very terse on this subject so  some 
  43.  
  44. actual code may help drive home the concepts involved.
  45.  
  46.                                 Background
  47.  
  48.      Before  plunging  into  a  discussion  of  memory  models,   a   brief 
  49.  
  50. introduction  to  the 8086 architecture is necessary.   This material  will 
  51.  
  52. help  to  illustrate  why there are different addressing  schemes  used  by 
  53.  
  54. different compilers.
  55.  
  56.      The  8086/88 microprocessors support 20-bit addressing.   This  allows 
  57.  
  58. the microprocessor to address in excess of one million bytes.  However, all 
  59.  
  60. the registers are 16-bits wide.  This implies that some segmentation scheme 
  61.  
  62. must  be  used  in order to address more than 64k  bytes  of  memory.   The 
  63.  
  64. technique used involves four 16-bit segment  registers:  CS,  DS,  ES,  SS.  
  65.  
  66. These  registers are the  code-segment,  data-segment,  extra-segment,  and 
  67.  
  68. stack-segment  registers respectively.   Depending on the instruction used, 
  69.  
  70. different segment registers come into play in determining the complete  20-
  71.  
  72. bit address.   In forming a complete address, the segment address is always 
  73.  
  74. shifted  left four bits.   Note that a segment register by itself addresses 
  75.  
  76. memory on 16-byte boundaries.   Sixteen byte regions addressable by segment 
  77.  
  78. registers  are  known  as  paragraphs.    While  paragraphs  and  paragraph 
  79.  
  80. alignment are not normally of interest to C programmers, they are sometimes 
  81.  
  82. important when developing assembly language interface code for C.
  83.  
  84.       When discussing long pointers, a special notation is used.  Since the 
  85.  
  86. address is split, it is written in the form:
  87.  
  88.                     segment:byte_pointer
  89.  
  90. where segment is the segment, and byte_pointer is the 16-bit low order part 
  91.  
  92. of  the  address.   A typical example of such an address would  be  "es:bx" 
  93.  
  94. which means `segment specified by es register and offset from this  segment 
  95.  
  96. specified  by  the  bx  register.'  This notation is  used  throughout  the 
  97.  
  98. listings included with this column.
  99.  
  100.      Machine  instructions  often differentiate between  inter  and  intra-
  101.  
  102. segment  operations.    For  example,  there  are  "near"  and  "far"  CALL 
  103.  
  104. instructions. 
  105.  
  106.      With this introduction, I will now outline several memory models. 
  107.  
  108.                              8080 Memory Model
  109.  
  110.      The  8080  memory model is just what the name  implies.   All  segment 
  111.  
  112. registers  are  set equal,  so that only a total of 64K is available for  a 
  113.  
  114. program.  This model is seen mostly under CP/M-86, but is occasionally used 
  115.  
  116. by  MS-DOS  programs.   None of the C compilers that I have  seen  restrict 
  117.  
  118. programs to this model.
  119.  
  120.                             Small memory model
  121.  
  122.      Many programs can work comfortably with only 64K of data space and 64K 
  123.  
  124. of  program space.   Such a model results when the CS and DS registers  are 
  125.  
  126. set to different blocks of memory (up to 64k each).   Normally,  ES and  SS 
  127.  
  128. are set equal to DS,  so that all data and stack memory resides in the same 
  129.  
  130. block  of  memory.   This  model  is  fine as long  as  programs  and  data 
  131.  
  132. requirements  are  small  enough to fit within  the  64k  limits.   Most  C 
  133.  
  134. compilers only support this model.
  135.  
  136.                             Large memory model
  137.  
  138.      In a large memory model, all addresses refer to the full 20-bit range.  
  139.  
  140. All subroutine calls are "far" calls, and all data is referred to with long 
  141.  
  142. pointers.   Long  pointers include a segment and byte address pointer (thus 
  143.  
  144. occupying 32-bits).  Only a few C compilers support this model.  The reason 
  145.  
  146. that most compilers don't support this model,  is the greater complexity of 
  147.  
  148. code generation.  I will mention more on this later.
  149.  
  150.                        Small Code / Large Data Model
  151.  
  152.      A useful hybrid of the small and large memory models is the one  where 
  153.  
  154. only 64k of program space is provided, but long pointers for data are used.  
  155.  
  156. This  model  offers speed advantages for programs which require  more  data 
  157.  
  158. storage, but are moderately small.
  159.  
  160.                        Large Code / Small Data Model
  161.  
  162.      One other possibility would be a large code / small data model,  which 
  163.  
  164. would  be  used  for programs with small data requirements but  large  code 
  165.  
  166. requirements.  
  167.  
  168.                             Large Stack Feature
  169.  
  170.      One type of model which has not been considered is one which  supports 
  171.  
  172. a  large  stack.   A  large  stack would support more than  64k  of  items.  
  173.  
  174. Implementing this feature would slow program execution significantly, since 
  175.  
  176. stack references would be complicated.   
  177.  
  178.                           Which model is better? 
  179.  
  180.      As long as a C program can fit within the small memory model, there is 
  181.  
  182. a  distinct speed advantage in using this model.   The large  memory  model 
  183.  
  184. produces  longer  (and  somewhat slower) programs because  of  the  greater 
  185.  
  186. generality  of each instruction produced (ability to refer to 1024k instead 
  187.  
  188. of 64k of memory requires longer pointers and more checks).  Since the 8086 
  189.  
  190. doesn't  provide many instructions to manipulate the  long  pointers,  many 
  191.  
  192. additional  instructions  must be generated for pointer related  operations 
  193.  
  194. (which also include all memory references.)  Specific examples of the  lack 
  195.  
  196. of  8086 instructions involves incrementing and decrementing long pointers.  
  197.  
  198. Note that a long pointer is not just a 32-bit word.  The upper 16-bits is a 
  199.  
  200. segment  address  which  must  be treated  accordingly  when  crossing  64k 
  201.  
  202. boundaries.  Examples  of  implementing  these  features  in  software  are 
  203.  
  204. included in Listing V. (llint.asm: examples: linc and ldec functions).
  205.  
  206.      Thus,  both models have drawbacks.   Speed is gained at the expense of 
  207.  
  208. (essentially) unlimited program/data space.  Use the large memory model for 
  209.  
  210. big programs which use big chunks of data.   Otherwise stick with the small 
  211.  
  212. model.
  213.  
  214.                     Drawbacks of the Small Memory Model 
  215.  
  216.      Assuming that you use the small memory model (by choice or because  of 
  217.  
  218. your  compiler),  everthing will run smoothly until it becomes necessary to 
  219.  
  220. deal  with memory outside of the C data address  space.   For  example,  it 
  221.  
  222. might  be nice to use large buffers for copying files,  or for keeping help 
  223.  
  224. information.  Another possibility would involve accessing special locations 
  225.  
  226. in the memory map.
  227.  
  228.      The  ability  to  use long pointers in a small  memory  model  can  be 
  229.  
  230. implemented  with  relative ease.   A set of such routines is presented  in 
  231.  
  232. Listings I.-V.   A description of the Long Pointer Package and applications 
  233.  
  234. for the package form the remainder of this column. 
  235.  
  236.                          The Long Pointer Package
  237.                                        
  238.      The  Long  Pointer  Package supplements a C  environment  by  allowing 
  239.  
  240. references to memory locations anywhere in the 20-bit address map.  This is 
  241.  
  242. done by defining a new data type LPTR (via a typedef):
  243.  
  244. .cp 7
  245.                         typedef union  __lptr
  246.                         {
  247.                             long    _llong;        /* long format */
  248.                             char    _lstr[4];   /* character format */
  249.                             LWORD    _lword;        /* long-word format */
  250.                         } LPTR;
  251.  
  252. where LWORD is defined as the following structure:
  253.  
  254.                         typedef struct __lword
  255.                         {
  256.                             unsigned _addr;     /* address */
  257.                             unsigned _segm;     /* segment */
  258.                         } LWORD;
  259.  
  260. This  format for LPTR makes the addresses defined directly compatible  with 
  261.  
  262. normal long pointers used at the assembly level.   These long pointers  are 
  263.  
  264. stored  in the 8080 style:  least significant byte of address  first,  most 
  265.  
  266. significant byte of segment last.
  267.  
  268.      The lowest level routines which support long memory references are, of 
  269.  
  270. necessity,  coded in assembly language.   The routines which implement many 
  271.  
  272. of  the lowest level functions in a non-compiler specific way are  included 
  273.  
  274. in Listing IV.  (llsup.asm).   Routines which implement functions for Aztec 
  275.  
  276. C86  (a typical 8086 C compiler) version 1.05i are included in  Listing  V. 
  277.  
  278. (llint.asm).  These routines may have to be modified for other C compilers, 
  279.  
  280. if register usage or stack arrangements differ.
  281.  
  282.      In order to actually use the routines with C programs, the header file 
  283.  
  284. "lsup.h" must be included at the beginning of modules which use or refer to 
  285.  
  286. LPTR  data types.   The "lsup.h" file refers to "_lsup.h" also.   These two 
  287.  
  288. headers are presented in listings  II. and III. respectively.
  289.  
  290.                             Supported Functions
  291.  
  292.      The  package supports a number of functions involving  long  pointers.  
  293.  
  294. There  are  routines to add offsets to long pointers,  copy memory  between 
  295.  
  296. long  pointers and routines to return data addressed by long  pointers.   A 
  297.  
  298. complete  list of these functions is included in Table I.   In this  table, 
  299.  
  300. the file in which the function is located is mentioned also.
  301.  
  302.      -------------------------- Table I. ----------------------------
  303.  
  304.      file: lsup.c   (some C support routines)
  305.  
  306.           lassign(dest,source)     assign long pointers
  307.           llstrcpy(dest,source)    long string copy
  308.           lprint(lptr)             debugging routine for printling LPTR's
  309.  
  310.  
  311.      file: llint.asm (Aztec C dependent support routines)
  312.  
  313.           flptr(lptr,sptr)         form a long pointer from a normal short
  314.                                    C (ds relative) pointer.
  315.           lchr(lptr)               return character addressed by long pointer.
  316.           lint(lptr)               returns int/unsigned addressed by long ptr. 
  317.           l_stchr(lptr,chr)        stores char at location lptr.
  318.           l_stint(lptr,intgr)      stores int  at location lptr.
  319.           lload(dest,lptr,len)     general purpose copy to short pointer
  320.                                    area (ds relative) from long pointer area
  321.           lstor(lptr,src,len)      reverse if lload()
  322.           linc(lptr)               increment long pointer
  323.           ldec(lptr)               decrement long pointer
  324.           ladd(lptr,offset)        add unsigned offset to lptr
  325.           lsub(lptr,offset)        subtract unsigned offset from lptr
  326.           lsum(lptr,offset)        add signed offset to lptr
  327.           lcopy(dest,src,len)      general purpose long to long copy
  328.                                    (can copy up to 1024k of memory)
  329.  
  330.           file: llsup.asm (compiler independent functions)
  331.     
  332.           linc                     increment a long pointer
  333.           ldec                     decrement a long pointer 
  334.           ladd                     add an unsigned offset to a long pointer
  335.           lsub                     sub an unsigned offset from a long ptr.
  336.           lsum                     add a signed offset to a long pointer
  337.           lcopy                    general copy routine.
  338.  
  339.      ---------------------- End of  Table I. ------------------------
  340.  
  341. .cp 3
  342.                                 An Example
  343.  
  344.      One  useful  application of long pointers under  MS-DOS  2.0  involves 
  345.  
  346. accessing a program's environment block.   The environment block is a Unix-
  347.  
  348. like  set  of environment variables and values.   This is normally used  to 
  349.  
  350. affect some particular aspects of program execution.   Specifics about  the 
  351.  
  352. enviroment address are included in Inset I.  Interested readers should also 
  353.  
  354. refer to the DOS 2.0 users manual for more details.
  355.  
  356.      -------------------------- Inset I. ----------------------------
  357.  
  358.                          Environment Block Address
  359.  
  360.     C  compilers  under MS-DOS normally produce .EXE files.   For  .EXE 
  361. files,  a  program segment prefix is created by DOS 2.0  and  higher.   The 
  362. segment  address of this prefix is es:0 when the user program  begins.   At 
  363. offset  002cH  from  this  address is stored the  segment  address  of  the 
  364. environment table.   Only a segment is stored:  the offset from the segment 
  365. is  again  zero.   Thus,  the  contents of es:2ch is  the  address  of  the 
  366. environment block.
  367.  
  368.     Normally,  C  compilers  have a maintenance routine which is  given 
  369. control at the start of program execution.  For Aztec C86,  this routine is 
  370. called  $begin  and is located in the calldos.asm module included with  the 
  371. compiler.  The user must define an external variable in calldos.asm for the 
  372. benefit  of env.c,  in order for the segment address to be accessible as  a 
  373. long pointer.  The procedure for this operation is detailed in the comments 
  374. included in Listing VI. (env.c)
  375.  
  376.                            Allocation of Memory
  377.  
  378.     If  a C program intends to use DOS memory allocation in  cojunction 
  379. with  the  long  pointers,  it  must also be sure  to  shrink  it's  memory 
  380. allocation  using the MS-DOS SETBLOCK function.   This is normally done  in 
  381. the initial maintenance routine of the C runtime system.   For Aztec C this 
  382. must be done in $begin.
  383.  
  384.      ---------------------- End of  Inset I. ------------------------
  385.  
  386.  
  387.      The example program env.c reads the environment block and displays the 
  388.  
  389. contents  of the whole block on the console.   In effect,  it provides  the 
  390.  
  391. same listing feature as the MS-DOS SET command.
  392.  
  393.                                 Conclusion
  394.  
  395.      In this column,  I have discussed various aspects of memory models for 
  396.  
  397. 8086  C  compilers.   I  have  included a set of C  and  assembly  language 
  398.  
  399. functions   which  support  long  pointers  under  a  small  memory   model 
  400.  
  401. environment.   With this package,  users can enjoy the best of both worlds: 
  402.  
  403. access  to  arbitrary  amounts/locations of  memory,  while  retaining  the 
  404.  
  405. efficiency of short pointers for regular code and pointer operations.   For 
  406.  
  407. compilers which only support the small model, this package allows access to 
  408.  
  409. features which were previously off-limits to 8086  C programmers.
  410.  
  411.  
  412.