home *** CD-ROM | disk | FTP | other *** search
/ C/C++ Interactive Guide / c-cplusplus-interactive-guide.iso / c_ref / csource3 / 137_01 / jun84col.ddj < prev    next >
Encoding:
Text File  |  1979-12-31  |  15.4 KB  |  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.  
  23.  
  24. support large memory models.  Before discussing some of the compilers which 
  25.  
  26.  
  27.  
  28. provide such features,  I thought it worthwhile to discuss the memory model 
  29.  
  30.  
  31.  
  32. concepts of the 8086 and how this impacts C compilers implemented for  this 
  33.  
  34.  
  35.  
  36. microprocessor  family.   I  will discuss the advantages and  drawbacks  of 
  37.  
  38.  
  39.  
  40. several memory models used by existing C compilers.  Code will be presented 
  41.  
  42.  
  43.  
  44. to help overcome some of the limitations of small memory model compilers.
  45.  
  46.      For  those  readers  who are not interested in the details of  8086  C 
  47.  
  48.  
  49.  
  50. compilers,  large  memory models,  or long pointers,  there is  still  some 
  51.  
  52.  
  53.  
  54. interesting material in this column.   Specifically, several routines which 
  55.  
  56.  
  57.  
  58. are  included  here illustrate real-life code to interface C  and  assembly 
  59.  
  60.  
  61.  
  62. language.   Most  compiler  manuals are very terse on this subject so  some 
  63.  
  64.  
  65.  
  66. actual code may help drive home the concepts involved.
  67.  
  68.                                 Background
  69.  
  70.      Before  plunging  into  a  discussion  of  memory  models,   a   brief 
  71.  
  72.  
  73.  
  74. introduction  to  the 8086 architecture is necessary.   This material  will 
  75.  
  76.  
  77.  
  78. help  to  illustrate  why there are different addressing  schemes  used  by 
  79.  
  80.  
  81.  
  82. different compilers.
  83.  
  84.      The  8086/88 microprocessors support 20-bit addressing.   This  allows 
  85.  
  86.  
  87.  
  88. the microprocessor to address in excess of one million bytes.  However, all 
  89.  
  90.  
  91.  
  92. the registers are 16-bits wide.  This implies that some segmentation scheme 
  93.  
  94.  
  95.  
  96. must  be  used  in order to address more than 64k  bytes  of  memory.   The 
  97.  
  98.  
  99.  
  100. technique used involves four 16-bit segment  registers:  CS,  DS,  ES,  SS.  
  101.  
  102.  
  103.  
  104. These  registers are the  code-segment,  data-segment,  extra-segment,  and 
  105.  
  106.  
  107.  
  108. stack-segment  registers respectively.   Depending on the instruction used, 
  109.  
  110.  
  111.  
  112. different segment registers come into play in determining the complete  20-
  113.  
  114.  
  115.  
  116. bit address.   In forming a complete address, the segment address is always 
  117.  
  118.  
  119.  
  120. shifted  left four bits.   Note that a segment register by itself addresses 
  121.  
  122.  
  123.  
  124. memory on 16-byte boundaries.   Sixteen byte regions addressable by segment 
  125.  
  126.  
  127.  
  128. registers  are  known  as  paragraphs.    While  paragraphs  and  paragraph 
  129.  
  130.  
  131.  
  132. alignment are not normally of interest to C programmers, they are sometimes 
  133.  
  134.  
  135.  
  136. important when developing assembly language interface code for C.
  137.  
  138.       When discussing long pointers, a special notation is used.  Since the 
  139.  
  140.  
  141.  
  142. address is split, it is written in the form:
  143.  
  144.                     segment:byte_pointer
  145.  
  146. where segment is the segment, and byte_pointer is the 16-bit low order part 
  147.  
  148.  
  149.  
  150. of  the  address.   A typical example of such an address would  be  "es:bx" 
  151.  
  152.  
  153.  
  154. which means `segment specified by es register and offset from this  segment 
  155.  
  156.  
  157.  
  158. specified  by  the  bx  register.'  This notation is  used  throughout  the 
  159.  
  160.  
  161.  
  162. listings included with this column.
  163.  
  164.      Machine  instructions  often differentiate between  inter  and  intra-
  165.  
  166.  
  167.  
  168. segment  operations.    For  example,  there  are  "near"  and  "far"  CALL 
  169.  
  170.  
  171.  
  172. instructions. 
  173.  
  174.      With this introduction, I will now outline several memory models. 
  175.  
  176.                              8080 Memory Model
  177.  
  178.      The  8080  memory model is just what the name  implies.   All  segment 
  179.  
  180.  
  181.  
  182. registers  are  set equal,  so that only a total of 64K is available for  a 
  183.  
  184.  
  185.  
  186. program.  This model is seen mostly under CP/M-86, but is occasionally used 
  187.  
  188.  
  189.  
  190. by  MS-DOS  programs.   None of the C compilers that I have  seen  restrict 
  191.  
  192.  
  193.  
  194. programs to this model.
  195.  
  196.                             Small memory model
  197.  
  198.      Many programs can work comfortably with only 64K of data space and 64K 
  199.  
  200.  
  201.  
  202. of  program space.   Such a model results when the CS and DS registers  are 
  203.  
  204.  
  205.  
  206. set to different blocks of memory (up to 64k each).   Normally,  ES and  SS 
  207.  
  208.  
  209.  
  210. are set equal to DS,  so that all data and stack memory resides in the same 
  211.  
  212.  
  213.  
  214. block  of  memory.   This  model  is  fine as long  as  programs  and  data 
  215.  
  216.  
  217.  
  218. requirements  are  small  enough to fit within  the  64k  limits.   Most  C 
  219.  
  220.  
  221.  
  222. compilers only support this model.
  223.  
  224.                             Large memory model
  225.  
  226.      In a large memory model, all addresses refer to the full 20-bit range.  
  227.  
  228.  
  229.  
  230. All subroutine calls are "far" calls, and all data is referred to with long 
  231.  
  232.  
  233.  
  234. pointers.   Long  pointers include a segment and byte address pointer (thus 
  235.  
  236.  
  237.  
  238. occupying 32-bits).  Only a few C compilers support this model.  The reason 
  239.  
  240.  
  241.  
  242. that most compilers don't support this model,  is the greater complexity of 
  243.  
  244.  
  245.  
  246. code generation.  I will mention more on this later.
  247.  
  248.                        Small Code / Large Data Model
  249.  
  250.      A useful hybrid of the small and large memory models is the one  where 
  251.  
  252.  
  253.  
  254. only 64k of program space is provided, but long pointers for data are used.  
  255.  
  256.  
  257.  
  258. This  model  offers speed advantages for programs which require  more  data 
  259.  
  260.  
  261.  
  262. storage, but are moderately small.
  263.  
  264.                        Large Code / Small Data Model
  265.  
  266.      One other possibility would be a large code / small data model,  which 
  267.  
  268.  
  269.  
  270. would  be  used  for programs with small data requirements but  large  code 
  271.  
  272.  
  273.  
  274. requirements.  
  275.  
  276.  
  277.                             Large Stack Feature
  278.  
  279.      One type of model which has not been considered is one which  supports 
  280.  
  281.  
  282.  
  283. a  large  stack.   A  large  stack would support more than  64k  of  items.  
  284.  
  285.  
  286.  
  287. Implementing this feature would slow program execution significantly, since 
  288.  
  289.  
  290.  
  291. stack references would be complicated.   
  292.  
  293.                           Which model is better? 
  294.  
  295.      As long as a C program can fit within the small memory model, there is 
  296.  
  297.  
  298.  
  299. a  distinct speed advantage in using this model.   The large  memory  model 
  300.  
  301.  
  302.  
  303. produces  longer  (and  somewhat slower) programs because  of  the  greater 
  304.  
  305.  
  306.  
  307. generality  of each instruction produced (ability to refer to 1024k instead 
  308.  
  309.  
  310.  
  311. of 64k of memory requires longer pointers and more checks).  Since the 8086 
  312.  
  313.  
  314.  
  315. doesn't  provide many instructions to manipulate the  long  pointers,  many 
  316.  
  317.  
  318.  
  319. additional  instructions  must be generated for pointer related  operations 
  320.  
  321.  
  322.  
  323. (which also include all memory references.)  Specific examples of the  lack 
  324.  
  325.  
  326.  
  327. of  8086 instructions involves incrementing and decrementing long pointers.  
  328.  
  329.  
  330.  
  331. Note that a long pointer is not just a 32-bit word.  The upper 16-bits is a 
  332.  
  333.  
  334.  
  335. segment  address  which  must  be treated  accordingly  when  crossing  64k 
  336.  
  337.  
  338.  
  339. boundaries.  Examples  of  implementing  these  features  in  software  are 
  340.  
  341.  
  342.  
  343. included in Listing V. (llint.asm: examples: linc and ldec functions).
  344.  
  345.      Thus,  both models have drawbacks.   Speed is gained at the expense of 
  346.  
  347.  
  348.  
  349. (essentially) unlimited program/data space.  Use the large memory model for 
  350.  
  351.  
  352.  
  353. big programs which use big chunks of data.   Otherwise stick with the small 
  354.  
  355.  
  356.  
  357. model.
  358.  
  359.                     Drawbacks of the Small Memory Model 
  360.  
  361.      Assuming that you use the small memory model (by choice or because  of 
  362.  
  363.  
  364.  
  365. your  compiler),  everthing will run smoothly until it becomes necessary to 
  366.  
  367.  
  368.  
  369. deal  with memory outside of the C data address  space.   For  example,  it 
  370.  
  371.  
  372.  
  373. might  be nice to use large buffers for copying files,  or for keeping help 
  374.  
  375.  
  376.  
  377. information.  Another possibility would involve accessing special locations 
  378.  
  379.  
  380.  
  381. in the memory map.
  382.  
  383.      The  ability  to  use long pointers in a small  memory  model  can  be 
  384.  
  385.  
  386.  
  387. implemented  with  relative ease.   A set of such routines is presented  in 
  388.  
  389.  
  390.  
  391. Listings I.-V.   A description of the Long Pointer Package and applications 
  392.  
  393.  
  394.  
  395. for the package form the remainder of this column. 
  396.  
  397.                          The Long Pointer Package
  398.                                        
  399.      The  Long  Pointer  Package supplements a C  environment  by  allowing 
  400.  
  401.  
  402.  
  403. references to memory locations anywhere in the 20-bit address map.  This is 
  404.  
  405.  
  406.  
  407. done by defining a new data type LPTR (via a typedef):
  408.  
  409. .cp 7
  410.                         typedef union  __lptr
  411.                         {
  412.                             long    _llong;        /* long format */
  413.                             char    _lstr[4];   /* character format */
  414.                             LWORD    _lword;        /* long-word format */
  415.                         } LPTR;
  416.  
  417. where LWORD is defined as the following structure:
  418.  
  419.                         typedef struct __lword
  420.                         {
  421.                             unsigned _addr;     /* address */
  422.                             unsigned _segm;     /* segment */
  423.                         } LWORD;
  424.  
  425. This  format for LPTR makes the addresses defined directly compatible  with 
  426.  
  427.  
  428.  
  429. normal long pointers used at the assembly level.   These long pointers  are 
  430.  
  431.  
  432.  
  433. stored  in the 8080 style:  least significant byte of address  first,  most 
  434.  
  435.  
  436.  
  437. significant byte of segment last.
  438.  
  439.  
  440.      The lowest level routines which support long memory references are, of 
  441.  
  442.  
  443.  
  444. necessity,  coded in assembly language.   The routines which implement many 
  445.  
  446.  
  447.  
  448. of  the lowest level functions in a non-compiler specific way are  included 
  449.  
  450.  
  451.  
  452. in Listing IV.  (llsup.asm).   Routines which implement functions for Aztec 
  453.  
  454.  
  455.  
  456. C86  (a typical 8086 C compiler) version 1.05i are included in  Listing  V. 
  457.  
  458.  
  459.  
  460. (llint.asm).  These routines may have to be modified for other C compilers, 
  461.  
  462.  
  463.  
  464. if register usage or stack arrangements differ.
  465.  
  466.      In order to actually use the routines with C programs, the header file 
  467.  
  468.  
  469.  
  470. "lsup.h" must be included at the beginning of modules which use or refer to 
  471.  
  472.  
  473.  
  474. LPTR  data types.   The "lsup.h" file refers to "_lsup.h" also.   These two 
  475.  
  476.  
  477.  
  478. headers are presented in listings  II. and III. respectively.
  479.  
  480.                             Supported Functions
  481.  
  482.      The  package supports a number of functions involving  long  pointers.  
  483.  
  484.  
  485.  
  486. There  are  routines to add offsets to long pointers,  copy memory  between 
  487.  
  488.  
  489.  
  490. long  pointers and routines to return data addressed by long  pointers.   A 
  491.  
  492.  
  493.  
  494. complete  list of these functions is included in Table I.   In this  table, 
  495.  
  496.  
  497.  
  498. the file in which the function is located is mentioned also.
  499.  
  500.      -------------------------- Table I. ----------------------------
  501.  
  502.      file: lsup.c   (some C support routines)
  503.  
  504.           lassign(dest,source)     assign long pointers
  505.           llstrcpy(dest,source)    long string copy
  506.           lprint(lptr)             debugging routine for printling LPTR's
  507.  
  508.  
  509.      file: llint.asm (Aztec C dependent support routines)
  510.  
  511.           flptr(lptr,sptr)         form a long pointer from a normal short
  512.                                    C (ds relative) pointer.
  513.           lchr(lptr)               return character addressed by long pointer.
  514.           lint(lptr)               returns int/unsigned addressed by long ptr. 
  515.           l_stchr(lptr,chr)        stores char at location lptr.
  516.  
  517.           l_stint(lptr,intgr)      stores int  at location lptr.
  518.           lload(dest,lptr,len)     general purpose copy to short pointer
  519.                                    area (ds relative) from long pointer area
  520.           lstor(lptr,src,len)      reverse if lload()
  521.           linc(lptr)               increment long pointer
  522.           ldec(lptr)               decrement long pointer
  523.           ladd(lptr,offset)        add unsigned offset to lptr
  524.           lsub(lptr,offset)        subtract unsigned offset from lptr
  525.           lsum(lptr,offset)        add signed offset to lptr
  526.           lcopy(dest,src,len)      general purpose long to long copy
  527.                                    (can copy up to 1024k of memory)
  528.  
  529.           file: llsup.asm (compiler independent functions)
  530.     
  531.           linc                     increment a long pointer
  532.           ldec                     decrement a long pointer 
  533.           ladd                     add an unsigned offset to a long pointer
  534.           lsub                     sub an unsigned offset from a long ptr.
  535.           lsum                     add a signed offset to a long pointer
  536.           lcopy                    general copy routine.
  537.  
  538.      ---------------------- End of  Table I. ------------------------
  539.  
  540. .cp 3
  541.                                 An Example
  542.  
  543.      One  useful  application of long pointers under  MS-DOS  2.0  involves 
  544.  
  545.  
  546.  
  547. accessing a program's environment block.   The environment block is a Unix-
  548.  
  549.  
  550.  
  551. like  set  of environment variables and values.   This is normally used  to 
  552.  
  553.  
  554.  
  555. affect some particular aspects of program execution.   Specifics about  the 
  556.  
  557.  
  558.  
  559. enviroment address are included in Inset I.  Interested readers should also 
  560.  
  561.  
  562.  
  563. refer to the DOS 2.0 users manual for more details.
  564.  
  565.      -------------------------- Inset I. ----------------------------
  566.  
  567.                          Environment Block Address
  568.  
  569.     C  compilers  under MS-DOS normally produce .EXE files.   For  .EXE 
  570.  
  571. files,  a  program segment prefix is created by DOS 2.0  and  higher.   The 
  572.  
  573. segment  address of this prefix is es:0 when the user program  begins.   At 
  574.  
  575. offset  002cH  from  this  address is stored the  segment  address  of  the 
  576.  
  577. environment table.   Only a segment is stored:  the offset from the segment 
  578.  
  579. is  again  zero.   Thus,  the  contents of es:2ch is  the  address  of  the 
  580.  
  581. environment block.
  582.  
  583.     Normally,  C  compilers  have a maintenance routine which is  given 
  584.  
  585. control at the start of program execution.  For Aztec C86,  this routine is 
  586.  
  587. called  $begin  and is located in the calldos.asm module included with  the 
  588.  
  589. compiler.  The user must define an external variable in calldos.asm for the 
  590.  
  591. benefit  of env.c,  in order for the segment address to be accessible as  a 
  592.  
  593. long pointer.  The procedure for this operation is detailed in the comments 
  594.  
  595. included in Listing VI. (env.c)
  596.  
  597.                            Allocation of Memory
  598.  
  599.     If  a C program intends to use DOS memory allocation in  cojunction 
  600.  
  601. with  the  long  pointers,  it  must also be sure  to  shrink  it's  memory 
  602.  
  603. allocation  using the MS-DOS SETBLOCK function.   This is normally done  in 
  604.  
  605. the initial maintenance routine of the C runtime system.   For Aztec C this 
  606.  
  607. must be done in $begin.
  608.  
  609.      ---------------------- End of  Inset I. ------------------------
  610.  
  611.  
  612.      The example program env.c reads the environment block and displays the 
  613.  
  614.  
  615.  
  616. contents  of the whole block on the console.   In effect,  it provides  the 
  617.  
  618.  
  619.  
  620. same listing feature as the MS-DOS SET command.
  621.  
  622.  
  623.                                 Conclusion
  624.  
  625.      In this column,  I have discussed various aspects of memory models for 
  626.  
  627.  
  628.  
  629. 8086  C  compilers.   I  have  included a set of C  and  assembly  language 
  630.  
  631.  
  632.  
  633. functions   which  support  long  pointers  under  a  small  memory   model 
  634.  
  635.  
  636.  
  637. environment.   With this package,  users can enjoy the best of both worlds: 
  638.  
  639.  
  640.  
  641. access  to  arbitrary  amounts/locations of  memory,  while  retaining  the 
  642.  
  643.  
  644.  
  645. efficiency of short pointers for regular code and pointer operations.   For 
  646.  
  647.  
  648.  
  649. compilers which only support the small model, this package allows access to 
  650.  
  651.  
  652.  
  653. features which were previously off-limits to 8086  C programmers.
  654.  
  655.  
  656.  
  657.