Chapter 11: Bonus Content
When I wrote Assembly Arithmetic Algorithms for DOS, I had planned for chapter 10 to be the final chapter. However, it seemed like a good idea to include the original source code for the C version of chastehex. Although this book is not about the C programming language, C programmers can gain insight from seeing the original 150 lines of code and comments of the main source file compared to the Assembly version.
I also want my readers to have a useful program that they can compile and run on any operating system or architecture. It is with great pleasure that I present the portable ANSI C version of chastehex!
chastehex main.c
1 /*
2 This is the original C version of chastehex upon which the Assembly versions were based.
3 */
4
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include "chastelib.h"
8
9 FILE* fp; /*file pointer*/
10 char bytes[17]; /*the byte buffer for hex and text dumping*/
11 int count=1; /*keeps track of how many bytes were read during each row*/
12
13 /*outputs the ASCII text to the right of the hex field*/
14 void textdump()
15 {
16 int a,x=0;
17
18 x=count;
19 while(x<0x10)
20 {
21 putstr(" ");
22 x++;
23 }
24
25 x=0;
26 while(x<count)
27 {
28 a=bytes[x];
29 if( a < 0x20 || a > 0x7E ){a='.';bytes[x]=a;}
30 x++;
31 }
32 bytes[x]=0;
33
34 putstr(bytes);
35 }
36
37 /*outputs up to 16 bytes on each row in hexadecimal*/
38
39 void hexdump()
40 {
41 int x,address=0;
42 x=0;
43 while((count=fread(bytes,1,16,fp)))
44 {
45 int_width=8;
46 putint(address);
47 putstr(" ");
48
49 int_width=2;
50 x=0;
51 while(x<count)
52 {
53 putint(bytes[x]&0xFF);
54 putstr(" ");
55 x++;
56 }
57 textdump();
58 putstr("\n");
59
60 address+=count;
61 }
62 putstr("EOF\n");
63 }
64
65 int main(int argc, char *argv[])
66 {
67 int argx,x,c;
68
69 radix=0x10; /*set radix for integer output*/
70 int_width=1; /*set default integer width*/
71
72 if(argc==1)
73 {
74 putstr
75 (
76 "Welcome to chastehex! The tool for reading and writing bytes of a file!\n\n"
77 "To hexdump an entire file:\n\n\tchastehex file\n\n"
78 "To read a single byte at an address:\n\n\tchastehex file address\n\n"
79 "To write a single byte at an address:\n\n\tchastehex file address value\n\n"
80 );
81 return 0;
82 }
83
84 if(argc>1)
85 {
86 fp=fopen(argv[1],"rb+");
87 if(fp==NULL)
88 {
89 putstr(argv[1]);
90 putstr("\nFailed to open file\n");
91 return 1;
92 }
93 else
94 {
95 putstr(argv[1]);
96 putstr("\n");
97 }
98 }
99
100 if(argc==2)
101 {
102 hexdump(); /*hex dump only if filename given*/
103 }
104
105 if(argc>2)
106 {
107 x=strint(argv[2]); /*extract a number from the argument string after the filename*/
108 fseek(fp,x,SEEK_SET); /*seek to the address given in argument*/
109 }
110
111 /*read a byte at address of second arg*/
112 if(argc==3)
113 {
114 c=fgetc(fp);
115 int_width=8;
116 putint(x);
117 putstr(" ");
118 if(c==EOF){putstr("EOF");}
119 else
120 {
121 int_width=2;
122 putint(c);
123 }
124 putstr("\n");
125 }
126
127 /*any arguments past the address are hex bytes to be written*/
128 if(argc>3)
129 {
130 argx=3;
131 while(argx<argc)
132 {
133 c=strint(argv[argx]);
134 int_width=8;
135 putint(x);
136 putstr(" ");
137 int_width=2;
138 putint(c);
139 putstr("\n");
140 fputc(c,fp);
141 x++;
142 argx++;
143 }
144 }
145
146 fclose(fp);
147 return 0;
148 }
149
150 /* gcc -Wall -ansi -pedantic main.c -o chastehex */
Of course, to compile the main.c file, you will need the C version of my standard library. For the most part, these functions are the same as the Assembly versions of the functions presented in this book. However, since C code can’t refer to register names, variable names were chosen based on how they were used. For example, “s” is usually a string, “c” is a character, and “count” is used for the number of bytes used in the fwrite function of the C standard library as part of the putstring function.
chastelib.h
1 /*
2 This file is a C library of functions written by Chastity White Rose. The functions are for converting strings into integers and integers into strings.
3 I did it partly for future programming plans and also because it helped me learn a lot in the process about how pointers work
4 as well as which features the standard library provides, and which things I need to write my own functions for.
5
6 As it turns out, the integer output routines for C are too limited for my tastes. This library corrects this problem.
7 Using the global variables and functions in this file, integers can be output in bases/radixes 2 to 36.
8
9 Although this code is commented, I have also written a readme.md file designed to explain the usage of these functions and the philosophy behind them.
10 */
11
12 /*
13 These following lines define a static array with a size big enough to store the digits of an integer, including padding it with extra zeroes.
14 The integer conversion function (intstr) always references a pointer to this global string, and this allows other C standard library functions
15 such as printf to display the integers to standard output or even possibly to files.
16 This string can be repurposed for absolutely anything I desire.
17 */
18
19
20 #define usl 0x100 /*usl stands for Unsigned or Universal String Length.*/
21 char int_string[usl+1]; /*global string which will be used to store string of integers. Size is usl+1 for terminating zero*/
22
23 /*radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal*/
24 int radix=2;
25 /*default minimum digits for printing integers*/
26 int int_width=1;
27
28 /*
29 The intstr function is one that I wrote because the standard library can display integers as decimal, octal, or hexadecimal, but not any other bases(including binary, which is my favorite).
30
31 My function corrects this, and in my opinion, such a function should have been part of the standard library, but I'm not complaining because now I have my own, which I can use forever!
32 More importantly, it can be adapted for any programming language in the world if I learn the basics of that language. That being said, C is the best language and I will use it forever.
33 */
34
35 char *intstr(unsigned int i) /*Chastity's supreme integer to string conversion function*/
36 {
37 int width=0; /*the width or how many digits including prefixed zeros are printed*/
38 char *s=int_string+usl; /*a pointer starting to the place where we will end the string with zero*/
39 *s=0; /*set the zero that terminates the string in the C language*/
40 while(i!=0 || width<int_width) /*loop to fill the string with every required digit plus prefixed zeros*/
41 {
42 s--; /*decrement the pointer to go left for correct digit placing*/
43 *s=i%radix; /*get the remainder of division by the radix or base*/
44 i/=radix; /*divide the input by radix*/
45 if(*s<10){*s+='0';} /*convert digits 0 to 9 to the ASCII character for that digit*/
46 else{*s=*s+'A'-10;} /*for digits higher than 9, convert to letters starting at A*/
47 width++; /*increment the width so we know when enough digits are saved*/
48 }
49 return s; /*return this string to be used by putstr,printf,std::cout or whatever*/
50 }
51
52 /*
53 This function prints a string using fwrite.
54 This algorithm is the best C representation of how my Assembly programs also work.
55 Its true purpose is to be used in the putint function for conveniently printing integers,
56 but it can print any valid string.
57 */
58
59 int putstring(const char *s)
60 {
61 int count=0; /*used to count how many bytes will be written*/
62 const char *p=s; /*pointer used to find terminating zero of string*/
63 while(*p){p++;} /*loop until zero found and immediately exit*/
64 count=p-s; /*count is the difference of pointers p and s*/
65 fwrite(s,1,count,stdout); /*https://cppreference.com/w/c/io/fwrite.html*/
66 return count; /*return how many bytes were written*/
67 }
68
69 /*
70 A function pointer named putstr which is a shorter name for calling putstring
71 But this doesn't exist just to save bytes of source files. Otherwise I wouldn't have these huge comments!
72 This exists so that all strings can be redirected to another function for output.
73 For example, if the strings were written to a log file during a game which didn't use a terminal.
74
75 But the most common use case is "putstr=addstr" when using the ncurses library to manage
76 terminal control functions for a text based game. Having the putstr pointer allows me to
77 include this same source file and use it for ncurses based projects.
78 */
79 int (*putstr)(const char *)=putstring;
80
81 /*
82 This function uses both intstr and putstring to print an integer in the currently selected radix and width.
83 */
84
85 void putint(unsigned int i)
86 {
87 putstr(intstr(i));
88 }
89
90 /*
91 The strint_errors variable is used to keep track of how many errors happened in the strint function.
92 The following errors can occur:
93
94 Radix is not in range 2 to 36
95 Character is not a number 0 to 9 or alphabet A to Z (in either case)
96 Character is alphanumeric but is not valid for current radix
97
98 If any of these errors happen, error messages are printed to let the programmer or user know what went wrong in the string that was passed to the function.
99 If getting input from the keyboard, the strint_errors variable can be used in a conditional statement to tell them to try again and recall the code that grabs user input.
100 */
101
102 int strint_errors = 0;
103
104 /*
105 The strint function is my own replacement for the strtol function from the C standard library.
106 I didn't technically need to make this function because the functions from stdlib.h can already convert strings from bases 2 to 36 into integers.
107 However, my function is simpler because it only requires 2 arguments instead of three, and it also does not handle negative numbers.
108 I have never needed negative integers, but if I ever do, I can use the standard functions or write my own in the future.
109 */
110
111 int strint(const char *s)
112 {
113 int i=0;
114 char c;
115 strint_errors = 0; /*set zero errors before we parse the string*/
116 if( radix<2 || radix>36 ){ strint_errors++; printf("Error: radix %i is out of range!\n",radix);}
117 while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
118 while(*s!=0)
119 {
120 c=*s;
121 if( c >= '0' && c <= '9' ){c-='0';}
122 else if( c >= 'A' && c <= 'Z' ){c-='A';c+=10;}
123 else if( c >= 'a' && c <= 'z' ){c-='a';c+=10;}
124 else if( c == ' ' || c == '\n' || c == '\t' ){break;}
125 else{ strint_errors++; printf("Error: %c is not an alphanumeric character!\n",*s);break;}
126 if(c>=radix){ strint_errors++; printf("Error: %c is not a valid character for radix %i\n",*s,radix);break;}
127 i*=radix;
128 i+=c;
129 s++;
130 }
131 return i;
132 }
133
134
135 /*
136 Those four functions above are the core of chastelib.
137 While there may be extensions written for specific programs, these functions are essential for absolutely every program I write.
138
139 The only reason you would not need them is if you only output numbers in decimal or hexadecimal, because printf in C can do all that just fine.
140 However, the reason my core functions are superior to printf is that printf and its family of functions require the user to memorize all the arcane symbols for format specifiers.
141
142 The core functions are primarily concerned with standard output and the conversion of strings and integers. They do not deal with input from the keyboard or files. A separate extension will be written for my programs that need these features.
143 */
If you come from a background of C programming, you may appreciate how much more readable the C version of the program is compared to the Assembly code. I will not deny that writing Assembly code takes longer and is harder for most people to understand.
However, most of my C programs were the initial prototypes before I wrote the Assembly versions of the same thing. I do this because the C Programming Language provides maximum portability and readability.
But, Assembly language provides the maximum efficiency and control of hardware that I require for my own satisfaction. By using the right combination of C and Assembly, I have the best of both worlds:
- C programs available for all devices and operating systems.
- Assembly programs optimized for the specific device or operating system that I am using.
There are other tools I have written besides chastehex. However, at this time, chastehex is the only one I have tested and improved enough to be confident that I am providing quality code that can be used, studied, shared, and modified.
When I said that chastehex is not just a program, but a philosophy, I mean that it embodies the four freedoms outlined in the GNU General Public License and also my philosophy of well-written code.
I believe that computer programming is a task for humans to tell computers what we want done. I teach people so that, like me, they can write better software, in any language, and not depend on software vendors who may decide to fix bugs in the software you purchased from them if you bribe them with enough money.
In the modern world, I view code as a fundamental human right because our lives depend on software that works with our hardware and does not work against us and our right to privacy and freedom of speech.