home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / C / ONE_ON_9.ZIP / ONE_ON_9.DOC < prev    next >
Encoding:
Text File  |  1988-03-07  |  18.1 KB  |  309 lines

  1.    This text is taken from an article by Jean-Pierre Schachter called DATA 
  2. STRUCTURES AND SCROLLING. It appeared in the February 1987, Volume 4, Num-
  3. ber 2, issue of Computer Language.
  4.  
  5.                             Start of text:
  6. ------------------------------------------------------------------------------
  7.    As users of MS-DOS know, the operating system contains programmer-ac-
  8. cessible system calls (interrupt 0x10, numbers 6 and 7) for scrolling the 
  9. screen. In fact, these interrupts are of limited usefulness since the 
  10. scrolling is not buffered and only vertical. While the interrupt could be used 
  11. even with a buffered arrangement in which one's text does not disappear 
  12. permanently off the edge of the screen, it is much simpler to ignore it 
  13. entirely and write one's own scrolling routine.
  14.    While a program called One_On_Nine has the practical objective of demon-
  15. strating a specific algorithm, it also has the more general and theoretical 
  16. objective of illustrating the use of superimposed and noncontiguous data 
  17. structures. Since understanding the latter will be helpful in commenting on 
  18. One_On_Nine, I'll start by discussing them.
  19.  
  20. SUPERIMPOSED AND NONCONTIGUOUS
  21.    Beginning programmers tend to imagine arrays as extended in three-dimen-
  22. sional space; needless to say, they begin to have trouble with arrays 
  23. dimensioned n > 3. They also tend to think of memory allocations as unique in 
  24. their structures, each having, necessarily, only one structure. These 
  25. assumptions are constricting and lead to unnecessarily complicated program-
  26. ming.
  27.    It is liberating to discover that no matter how complex the data structure
  28. in a declaration or a programmer's mind, in the machine it's just a linear 
  29. sequence of contiguous bytes. What makes an array an array is the complex 
  30. incrementation automatically implemented by a high-level language that lets 
  31. one step through this linear sequence in a regular and predetermined way.
  32.    The machinery that makes this incrementation possible is invisible to
  33. the user who is given information only on a need-to-know basis. While lower-
  34. level languages also provide the invisible incrementation devices, in addition 
  35. they give the programmer the tools to construe and reconstrue his or her data 
  36. any which way he or she can. Two useful examples of creativity in data 
  37. structuring involve whae I call superimposed data structures and noncontiguous 
  38. data structures.
  39.    If a language provides a way of declaring a data pattern without a sim-
  40. ultaneous allocation of memory, then the way is usually clear for deciding 
  41. where in memory the pattern is to begin. This implies that the same physical 
  42. region of memory can be treated as having more than one structure. A given 
  43. region of memory can, for example, be treated at one time as a collection of 
  44. chars or bytes and at another as a collection of ints.
  45.    Were this all, this principle would be merely of mild interest, but it
  46. works with declared data types as well as those that come native to the 
  47. language. Thus in C the ability to declare structs and unions provides the 
  48. means for superimposing different data patterns on one in an unlimited num-
  49. ber of ways.
  50.    While this capability is native to C, it can also be found in an extrem-
  51. ely convenient form as an extension of standard Pascal in Turbo Pascal, the 
  52. Borland International implementation. Not only does Turbo allow for pointers 
  53. but also for the declaration of variables at specified addresses, which in 
  54. turn allows for the superimposition of data sturctures. Later we will see how 
  55. this works in C.
  56.    While superimposition counteracts the superstition that a given region
  57. of memory is what it was declared and not something else, another liberation 
  58. still has to take place: liberation from the belief that separated or 
  59. noncontiguous regions of memory may never come together into one data 
  60. structure. It is true that memory made available by the alloc() family of 
  61. functions and by array declarations consists of contiguous blocks of RAM, but 
  62. these are not the only ways of dealing with memory. When data structures are 
  63. created by means of arrays of pointers that subsequently are initialized, any 
  64. region can become unified with any other. (This will be shown in the code.)
  65.  
  66. ONE_ON_NINE
  67.    The objective of One_On_Nine is to create a virtual screen in memory 
  68. that is nine times the size of the visible portion of the acrual display. The 
  69. virtual memory is best imagined as a tic-tac-toe grid that initially has its 
  70. central panel mapped one to one with the visible display (more precisely, one 
  71. to two). The problem faced in dealing with this virtual screen is that the 
  72. subportion of the allocated memory we wish to manipulate is not itself a 
  73. contiguous, linear sequence of bytes (Figure 1).
  74.  
  75.  
  76. SUBPORTION'S NONCONTIGUOUS SEQUENCE:
  77.  
  78. Beginning of allocation
  79. A|----------------------------------------------------------| ----
  80.  |                                              ^           |D  ^
  81.  |<----------------------240 bytes--------------|---------->|   |
  82.  |                                              |           |   |
  83.  |                                              | 25        |   |
  84.  |                   B                   C      |           |   |
  85.  |                   |-------------------| -----\/          |   |
  86.  |                   |                   |      ^           |   |
  87.  |<----80 bytes----->|<----80 bytes----->|      |           |   |
  88.  |                   |                   |      | 25        |   | 75
  89.  |                   |                   |      |           |   |
  90.  |                   |                   |      |           |   |
  91.  |                   |-------------------| -----\/          |   |
  92.  |                                                          |   |
  93.  |                                                          |   |
  94.  |                                                          |   |
  95.  |                                                          |   |
  96.  | B is located at A + ((240*25) + 80)                      |   |
  97.  |                                                          |   |
  98.  |----------------------------------------------------------| --\/
  99.                                                              E
  100.  
  101.                   ******* FIGURE 1 *******
  102.  
  103.    In Figure 1, the region from A to E is allocated to the pointer pb and
  104. consists  of a contiguous sequence of bytes. We are interested, however, in 
  105. having efficient access to 25 noncontiguous sequences within that allocation, 
  106. namely the 25 strings between the columns B and C, which are separated from 
  107. each other by memory between A and B and between C and D. It should be clear 
  108. that no array making direct reference to memory can solve this problem.
  109.    The strategy of One_On_Nine is to declare an array of 25 pointers, each
  110. of which is initialized to one of the 25 addresses immediately below B. By 
  111. shifting these addresses systematically, different 25-by-80 sections of the 
  112. larger virtual screen can be accessed. When the program is run, it begins by 
  113. filling the display with alpha garbage for display purposes. It then waits for 
  114. input. Acceptable input is limited to the arrow keys, the escape key, with all 
  115. other keys yielding BEEP. As one would expect, the arrow keys allow the user 
  116. to scroll in any direction within the limits of the 75-by-240 grid.
  117.    The code for One_On_Nine was written in Microsoft C and may have two un-
  118. familiar features. (The code also compiles in Quick C -typist)  First, the 
  119. Microsoft compiler allows for the creation of pointers to segments beyond the 
  120. 128K mark (far pointers), a real convenience when dealing with screen memory 
  121. that begins (under MS-DOS and with a composite monitor at 0xb8000, and with a 
  122. monochrome monitor at 0xb0000). Second, a quick look at the function
  123. get_scan() will show that it makes use of an interrupt calling function that 
  124. is fairly common but not System V.  (I wrote get_scan() for user input because 
  125. I wanted access to the arrow key scan codes as well as a non-echoing input 
  126. requiring no <cr>. The ability to recognize the arrow keys, however, is not 
  127. essential to the program and need not detain us; I'll comment later on the use 
  128. of far pointers.)
  129.    After the macros and headers, One_On_Nine starts its declarations. First,
  130. notice how few there are; second, notice how simple they are. There are only 
  131. two global declarations: *pb, a pointer to char that points to the virtual 
  132. screen, and line[], a 25-element array of pointers to char that will be the 
  133. basis of our mapping from virtual screen to visible display.
  134.    The main() module of One_On_Nine contains very little beyond function
  135. calls. The static char array clrscr[] provides the ANSI screen-clearing string 
  136. for the macro CLRSCR. The clrscr[] declaration is of interest only because it 
  137. illustrates how an initialized static array can be used to hold a string 
  138. constant containing unprintable characters in a program. In One_On_Nine, the 
  139. unprintable character was the escape required for the ANSI clear-screen 
  140. sequence.
  141.    Aside from the screen-clearing code, the only other line that is not a 
  142. simple function call is (the memset() call), which simulaneously allocates 
  143. 18,000 bytes and sets it to blanks.
  144.  
  145. MOVE IT
  146.    The central function in One_On_Nine is the brief move_it() function. 
  147. Move_it() sets the addresses in the pointer array line[], which is also used 
  148. by the function show_it(). Show_it() is the function that copies the 
  149. designated rectangle from the linear memory pointed by pb to the display. It 
  150. is line[] then that holds the starting addresses of the 25  80-element 
  151. regions; show_it() simply uses those addresses to find it's material for 25 
  152. copies.
  153.    The system is primed by setting line[]'s sddresses to the central rectan-
  154. gle by means of a move_it() call on pb plus 6080 (the upper left-hand corner 
  155. of the central rectangle region, 6080 being equal to (25*240) + 80). After 
  156. this, the arrow keys are used simply to increment or decrement pb with a call 
  157. to move_it(), thereby shifting the mapping between display and virtual screen. 
  158. Given that the heart of the action lies in move_it(), this function warrants 
  159. closer inspection.
  160.    Move_it() does something interesting. So far we have been concentrating
  161. simply on the use of pointer arrays in the accessing of noncontiguous memory 
  162. blocks. This device can be greatly enhanced by using suitably defined structs 
  163. and pointer arithmetic. The feature of the struct that we tap in move_it() is 
  164. that incrementation on a pointer to a struct is based on the size of the 
  165. scruct itself; this means we have a very neat way of setting step sizes for 
  166. stepping through a region of memory.
  167.    As already mentioned, move_it() initializes the pointer array line[]; we
  168. are now in a position to see how it does this. While the call to move_it()
  169. uses pb (declared as a pointer to char), the parameter of move_()--start--
  170. is declared a pointer to a struct, gapl. Gapl's sole element is an array of 
  171. 240 chars. You must remember that in this case only memory for a local pointer 
  172. has been allocated.
  173.    In a call to move_it(), start gets it's initial (address) value from 
  174. pb, assigns it to line[0], and then cycles through buffer[] in 240 char hops, 
  175. leaving it's pb address in the next element of line[] each time it touches 
  176. down. The new addresses in line[] are then used by show_it() to refresh or 
  177. update the display.
  178.    It should also be noted that the reason for using register variables in
  179. move_it(), as well as some of the other functions, lies in the need for 
  180. execution speed. When execution is slow, lateral scrolling can show an 
  181. unpleasant wave through the display as the screen is refreshed from top to 
  182. bottom. With the machine running at 8 Mhz, One_On_Nine shows almost no wave, 
  183. though at 4.77 MHz the wave is visible.
  184.  
  185. SHOW IT
  186.    While move_it() is the function at the heart of One_On_Nine, show_it()
  187. also deserves discussion because it illustrates how a direct memory-to-memory
  188. swap is possible between a buffer and the screen.
  189.    The problem in such a swap lies in addressing high memory, in this case
  190. memory beginning at 0xb0000, and initializing a pointer to that memory. Whether 
  191. and how this can be done depends on the specific compiler used. The Microsoft 
  192. compiler has two ways: using the large-memory model or using a far pointer.
  193.    Show_it() uses the second method and also demonstrates an elegant tech-
  194. nique of initialization. The first thing to understand is that while far 
  195. pointers use 32 bits instead of the usual 16, they do not resemble long ints 
  196. but rather a two-cell array of ints. Because of MS-DOS's use of segment-offset
  197. arithmetic in it's addressing, the far pointer requires the segment address in 
  198. it's left 16 bits and the offset in it's right 16 bits. Unless one is 
  199. attempting to reach a byte at an address that is not a multiple of 16, one can 
  200. leave the offset at 0 and set the segment to the actual address desired 
  201. divided by 16.
  202.    Since 0xb0000 (720,896) is a multiple of 16 (720,896/16 = 45,056), all we
  203. need do to initialize the far pointer is put 45,056 (0xb000) into it's left 16 
  204. bits. Since the shift operation leaves 0's behind as it shifts a bit pattern, 
  205. the code << 16 in show_it() manages in one move to initialize both of the far 
  206. pointer's sides, the segment and the offset.
  207.    The standard way of doing this with the Microsoft compiler is to use the
  208. provided macros FP_SEG() and FP_OFF(). For example, if the far pointer was 
  209. named distant and the address was 0xb0000, the two lines:
  210.  
  211. FP_SEG(distant) = 0xb000;
  212. FP_OFF(distant) = 0;
  213.  
  214. would place 0xb000 in distant's segment cell and 0 in it's offset cell.
  215.    The other thing to notice about show_it() is that the device of declaring
  216. pointers to structures is used again, once to shift lines but also once to 
  217. skip over bytes. The latter is necessary because one page of screen memory is 
  218. actually twice the size of the rectangle we have defined within the virtual 
  219. screen. The reason for this is that each byte of screen memory has a companion 
  220. that sets the displayed byte's attributes (such as intensity, blinking, 
  221. reverse, etc.) and must be skipped before reaching the next visible byte. The 
  222. rest of show_it() simply works its way through the virtual rectangle and 
  223. display memory by incrementing the pointers to structs.
  224.    We can now see how superimposed and noncontiguous data structures can be
  225. used. It should be clear that in One_On_Nine a simulated array is continually 
  226. moved around over the surface of a much larger character array. It should also 
  227. be clear that the array being moved is itself noncontiguous, since it consists 
  228. for all practical purposes of 24 discrete 80-element char strings. We have 
  229. been moving a noncontinuous array over the surface of another, larger array.
  230.  
  231. SPECIAL CASES AND FUNCTIONS
  232.    But while the kinds of data structure I have been describing add consid-
  233. erable power to the programmer's arsenal, it must be pointed out that with 
  234. regular use a need arises for a specialized set of supporting functions. Since 
  235. these structures are all based on the use of pointer arrays, functions are 
  236. wanted that take pointer arrays as their parameters but operate on the values 
  237. pointed to by their elements, not the arrays.
  238.    The common C function puts(char*), for example, takes a pointer to char as
  239. it's parameter, the pointer pointing to the beginning of a string of 
  240. characters in memory. What if, however, we did not have a contiguous sequence 
  241. of characters to output but rather a sequence of characters at various loca-
  242. tions listed by their addresses in an array ?
  243.    These two cases are compared here. Case 1 is puts(). Case 2 requires a 
  244. function that cycles through an array of pointers, putting out the content at 
  245. the address contained in each successive cell of the array:
  246.  Case 1.  Go to add1, then output what is contained there. Do this for each
  247. successive add until it's contained char is \0:
  248.  
  249. add1 [address of the beginning of the character array]
  250. C | A | T | \0
  251.  
  252. Case 2. Go to add1, then output what is contained at the address in add1,viz.
  253. [ADD1]. Do this for each successive add until you reach addn:
  254.  
  255. add1 [the beginning of the pointer array]
  256. [ADD1] | [ADD2] | [ADD3]
  257.   C    |   A    |   T
  258.  
  259.    The function ptr_puts() below does exactly this:
  260.  
  261. #define NEW_LINE; putchar(13);
  262.    putchar(10)
  263. ptr_puts(ptray, chnum)
  264. struct ptopt {
  265.    char *ptchr;
  266. } *ptray;
  267. int chnum; 
  268. {
  269.    while(chnum--)
  270.       putchar(*(ptray++)->ptchr);
  271.    NEW_LINE;
  272. }
  273.  
  274. while the following function, ptr_cpy(), is a pointer-array version of the
  275. common function strncpy(string1, string2, n), which works on character arrays:
  276.  
  277. ptr_cpy(dest, origin, chnum)
  278. struct oneptr {
  279.    char *ptchar;
  280. } *dest, *origin;
  281. int chnum;
  282. {
  283.    while (chnum--)
  284.       *(dest++)->ptchar = *(origin++)->ptchar;
  285.    NEW_LINE;
  286. }
  287.  
  288.    The only tricky thing about these functions is that they declare the 
  289. incoming pointer array as a pointer-to-pointer to char, which allows for 
  290. incrementing on the array addresses while at the same time giving access to 
  291. the data pointed to by each element of the array being incremented. Without
  292. the pointer-to-pointer declaration, increminting and decrementing would take 
  293. place on the address of the first data element, which is not what is wanted. 
  294. What is wanted is incrementation through the array holding the addresses, 
  295. which is what one gets in ptr_puts() and ptr_cpy().
  296.    Put in terms of Case 2, the code must be written in such a way that the
  297. pointer is incremented on add1, not [ADD1]. There is no telling what is con-
  298. tained in [ADD1]++, but in add1++ we can find the next pointer to the next 
  299. char we wish to put to screen. The function ptr_puts() writes to display the 
  300. values pointed to by each element of a pointer array. On the other hand, 
  301. ptr_cpy() copies the values pointed to by one pointer array's elements to the 
  302. memory locations pointed to by another pointer array's elements.
  303.    For applications making regular use of pointer arrays, one would probably
  304. want to develop a library of such functions closely paralleling the functions 
  305. known to be useful for dealing with straightforward linear arrays.
  306.  
  307.                            End of text
  308. ----------------------------------------------------------------------------
  309.