Tag: c

  • Chastity Windows Reverse Engineering Notes

    The Windows version of FASM includes header files for the Windows API. It also includes some examples, but not a single one of them were a simple “Hello World” console program.

    Fortunately, I was able to find one that actually assembled and ran on the FASM forum. Below is the source code.

    format PE console
    include 'win32ax.inc'
    .code
    start:
    invoke  WriteConsole, <invoke GetStdHandle,STD_OUTPUT_HANDLE>,"Hello World!",12,0
    invoke  ExitProcess,0
    .end start
    

    To get it working required me to keep the include files in a location I remembered and set the include environment variable. I found this out from the FASM Windows documentation.

    set include=C:\fasm\INCLUDE

    However, there was another problem. The example that I found online and got working uses macros, most specifically, one called “invoke”. While this works if you include the headers, it hides the details of what is actually happening. Therefore, I decided to reverse engineer the process by using NOP instructions to sandwich the bytes of machine code.

    90 hex is the byte for NOP (No OPeration). So to extract the macro call that exits the program, I use this.

    db 10h dup 90h
    invoke  ExitProcess,0
    db 10h dup 90h
    

    Then I disassemble the executable and find the actual instructions given.

    ndisasm main.exe -b 32 > disasm.txt

    As simple as this method is, it actually works. For example, this output is given as part of the output.

    0000022D  90                nop
    0000022E  90                nop
    0000022F  90                nop
    00000230  90                nop
    00000231  90                nop
    00000232  90                nop
    00000233  90                nop
    00000234  90                nop
    00000235  90                nop
    00000236  90                nop
    00000237  90                nop
    00000238  90                nop
    00000239  90                nop
    0000023A  90                nop
    0000023B  90                nop
    0000023C  90                nop
    0000023D  6A00              push dword 0x0
    0000023F  FF1548204000      call dword near [0x402048]
    00000245  90                nop
    00000246  90                nop
    00000247  90                nop
    00000248  90                nop
    00000249  90                nop
    0000024A  90                nop
    0000024B  90                nop
    0000024C  90                nop
    0000024D  90                nop
    0000024E  90                nop
    0000024F  90                nop
    00000250  90                nop
    00000251  90                nop
    00000252  90                nop
    00000253  90                nop
    00000254  90                nop
    

    There can be no mistake that it is that location between the NOPs where the relevant code is. Therefore, I replaced the macro that exits the program with this.

    ;Exit the process with code 0
     push 0
     call [ExitProcess]
    

    What I learned

    As I repeated the same process for the other macros, I found that the way system calls in Windows work is that the numbers are pushed onto the stack in the reverse order they are needed. I was able to decode the macros and get a working program without the use of “invoke”. Here is the full source!

    format PE console
    include 'win32ax.inc'
    
    main:
    
    ;Write 13 bytes from a string to standard output
    push 0              ;this must be zero. I have no idea why!  
    push 13             ;number of bytes to write
    push main_string    ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteConsole] ;all the data is in place, do the write thing!
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    main_string db 'Hello World!',0Ah
    

    I don’t know much about the Windows API, but I did discover some helpful information when I searched the names of these functions that were part of the original macros.

    https://learn.microsoft.com/en-us/windows/console/getstdhandle
    https://learn.microsoft.com/en-us/windows/console/writeconsole

    Why I did this

    You might wonder why I even bothered to get a working Windows API program in Assembly Language. After all, I am a Linux user to the extreme. However, since Windows is the most used operating system for the average person, I figured that if I write any useful programs in Assembly for 32-bit Linux, I can probably port them over to Windows by changing just a few things.

    Since my toy programs are designed to write text to the console anyway and I don’t do GUI stuff unless I am programming a game in C with SDL, I now have enough information from this small Hello World example to theoretically write anything to the console that I might want to in an official Windows executable.

    Obviously I need to learn a lot more for bigger programs but this is the first Assembly program I have ever gotten working for Windows, despite my great success with DOS and Linux, which are easier because they are better documented and ARE TAUGHT BETTER by others. People programming Assembly in Windows have been ruined by macros which hide the actual instructions being used. As I learn how these things work, I will be sure to pass on the information to others!

  • Assembly Arithmetic Algorithms 16-bit DOS Edition

    Assembly Arithmetic Algorithms 16-bit DOS Edition

    Preface

    You might be surprised to find a book in the 21st century about programming in Assembly Language on DOS. For many people, DOS seemingly disappeared and became irrelevant in the mid 1990s. Fortunately for me, this was not the case. In fact I regularly used DOS on old computers my mother’s piano students gave me that they no longer wanted. I had an MS-DOS 3.3 Manual, floppy disks of both 5.5 inch and 3.5 inch sizes, software like Word Perfect and Edlin that you have probably never heard of, and I memorized how to write, copy, rename, and delete files without Microsoft Windows or Linux, both of which I did not have until the 21st century when I was 14 years old and got my first modern laptop which had Windows 98 and the ability to restart into DOS mode.

    As you might expect, I spent more time in DOS than I did on Windows 98. If it had not been for the battery failure, I probably would have used it much longer than I did. A world of text terminals was my playground and I was not used to moving a mouse and clicking in a Windows graphical user interface. I used Windows 98 to download DOS games from the internet and to play “Castle of the Winds” (an obscure Norse Mythology game you probably also never heard of).

    I tell you all this for context so that you understand why the Magic of older computer systems is still with me even as I write this in the year 2025. Just because MS-DOS and Windows 98 are no longer commonly installed on computers does not mean that the old games or programming styles have disappeared, at least not yet. Thanks to emulators like DOSBox and real operating systems like FreeDOS, it is possible to get programs created 40+ years ago to still run.

    But I wanted to go a step further and write new programs that could run within an emulator and theoretically a computer made in the 1980s. However, the information is getting harder to find. I want to thank the authors, both dead and alive, who have worked to make sure this information was freely available on the internet. In particular, I would most like to thank Randall Hyde (author of “The Art of Assembly”) and Ralf Brown (creator of “Ralf Brown’s Interrupt List”). Without this information, I might have never figured out how to even write “Hello World!” in DOS 16-bit Assembly Language

    Therefore, I encourage anyone brave enough to read this book to consider that I am just a nerd that feared this information would be lost forever unless I pass on the information compiled by these genius men who have worked hard to help people learn how to accomplish tasks in Assembly Language for old operating systems that are now only known by those who truly seek to understand how computers work!

    Introduction

    First, let me introduce this book by telling you what I will teach you. By the end of this book, you will have enough information to write any text based console program in the form of a 16-bit DOS (Disk Operating System) “.com” file.

    The “.com” file was a format used by all version of MS-DOS, and even supported on Windows up to XP. It has no header information and is limited to 64 kilobytes of memory. Rather than viewing the limitation as a weakness, I view it as a strength because it forces me to be a better programmer and squeeze the most out of every byte.

    Required Knowledge

    To get the most out of this book, some background on the Binary and Hexadecimal numeral systems is going to be helpful, but this is not strictly required because I will be providing functions you can use in your code that will convert between decimal (base ten), binary (base two), and hexadecimal (base 16).

    However, I would say that experience in at least one programming language is necessary for an understanding of terminology like “arrays”, “pointers”, “addresses”, “integers”, “floating point”, etc. I recommend the C Programming Language as a start. C++ is also a good starting language but tends to abstract details away that directly apply to Assembly Language, which is the lowest level a human can go for understanding a computer.

    Low Level

    Low level is a term that confuses people. People think something high level is better than low level. In simple terms, humans consider themselves superior to machines and therefore think themselves higher or more important because of their abstract though.

    A computer thinks only in terms of numbers. A computer may not understand “high level” abstractions such as love, religion, philosophy, etc, but that is not its job. A computer must add, subtract, multiply, and divide. These are the four arithmetic functions which many human struggle to do.

    Therefore I ask you, between a human and a computer, who is really low level or high level? In the age of Artificial Intelligence taking over human jobs and beating humans at Chess, we would all do well to take this question seriously.

    I wrote this book because I think like a machine and I hope to help others think this way because it is the best way to learn programming and control your computer by writing Assembly Language programs or to go back to your favorite programming language with a greater understanding of why things work as they do.

    Why DOS?

    DOS is not at all like Windows or Linux, because it comes from an older time when people were expected to read books and even video games often came in the form of source code published in books. Therefore, I have decided to dedicate this book to the Disk Operating System more commonly called DOS and made famous by MS-DOS which was Microsoft’s version that people in the 80s and 90s remember. Later on, I plan to write a book on programming on Linux using similar but modern methods.

    Online Example Programs

    Although you can retype every example program from this book and try to run it in your DOS emulator. I also provide the examples as downloads from my Github repository for teaching Assembly.

    My samples are free and under the “GNU General Public License v3.0” because my intention is to make Assembly Language easy to learn for everyone without any restrictions. My hope is that others find the joy of programming in DOS, no matter whether they learn it from me or whether they learned it from someone else who may have picked up a few things from me.

    Chapter 1: The First Program

    For this chapter, I will explain give the source code of an example program that works in DOS, how to assemble it using the tools FASM or NASM, and finally, how the program works line by line.

    First, here is the source code of a program that looks like nonsense but does something really cool.

    org 100h
    
    mov ah,2
    mov dl,20h
    loop_start:
    int 21h
    inc dl
    cmp dl,7Fh
    jne loop_start
    
    mov ax,4C00h
    int 21h
    

    You will need an assembler. My first recommendation is FASM, the Flat Assembler.

    You can download FASM and install it by putting it in your path. The instructions for doing this depend on your operating system but it can be done on Windows, Linux, or even within a DOS operating system, which you will of course need to run the program.

    Assemble with FASM

    To assemble this program with FASM, place the source in a file named “main.asm” and run this command

    fasm main.asm

    FASM will automatically create a “main.com” file because it understands by the context of “org 100h” that you are intending to create a DOS executable that ends with a “.com” extension. This directive signals that the program starts at address 100 hexadecimal or 256 decimal. This kind of DOS program always starts at that address.

    After you have created the “main.com” file, you will need some kind of DOS emulator to run it. I recommend DOSBox because it is easy to set up and has a lot of documentation to help you.

    As an example of how to use DOSBox efficiently, I have added the path of my working directory where I test my programs directly into my DOSBox configuration file. Instructions for doing this depend on your host operating system. Consult the DOSBox documention for the location of where it will be on your operating system.

    [autoexec]
    # Lines in this section will be run at startup.
    # You can put your MOUNT lines here.
    mount d ~/git/Chastity-Code-Cookbook/work/
    

    This mounts a folder on my Linux system as if it was the D drive recognized by DOS. Back in the DOS and early Windows days, there were “drives” which were all a single letter. A and B were the floppy disk drives, C was the hard disk drive, and sometimes there was a D or E drive for a CDROM drive. DOSBox lets you emulate them and mount any folder on the host operating system (usually Windows or Linux) and access it as you would in DOS.

    To switch to the D drive, I just enter

    D:

    And then I can type “dir” to see the files, and then I can type

    main

    and the main.com file will run. This works because “.com” and “.exe” files are seen by DOS as a program that can be executed or run.

    When you run the program, it will display

     !"#$%&amp;'()*+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
    

    Basically the program is displaying every printable character. This is the correct behavior I expected when I wrote the program.

    Assemble with NASM

    You can assemble the example program with NASM instead of FASM if you wish

    nasm main.asm -o main.com
    

    Disassembling the Program

    If you have a disassembler, it is possible to extract the source code from the main.com binary file! I always used “ndisasm” for this because it usually comes installed along with NASM.

    ndisasm -o 0x100 main.com
    

    If you disassemble it like this, you will get this as a result:

    00000100  B402              mov ah,0x2
    00000102  B220              mov dl,0x20
    00000104  CD21              int 0x21
    00000106  FEC2              inc dl
    00000108  80FA7F            cmp dl,0x7f
    0000010B  75F7              jnz 0x104
    0000010D  B8004C            mov ax,0x4c00
    00000110  CD21              int 0x21
    

    You will see that it is almost identical to the source except that the loop_start label has been replaced with the number 104h. This is because at the machine code level, there are only numbers.

    The first column in the ndisasm output is the address of the instruction. The second are the actual machine code bytes. The third column are the approximation of the original source code. It has small differences but it is close enough that we can figure out which program it was that was assembled!

    Now let me break down why it works by repeating the source but including comments this time

    org 100h       ;tell assembler that code begins running at this address
    
    mov ah,2       ; move (copy) the number 2 into the ah register
    mov dl,20h     ; move the number 20 hex=32 dec into the dl register
    loop_start:    ; the loop starts here
    int 21h        ; interrupt 21 hex=33 dec for a DOS system call
    inc dl         ; add 1 to the dl register
    cmp dl,7Fh     ; compare the dl register with 7F hex = 127 dec
    jne loop_start ; Jump if Not Equal to loop_start
    
    mov ax,4C00h   ; DOS exit call with ah=4C and return al=0
    int 21h        ; DOS system call to complete the exit function
    

    I know you are probably a little bit confused at this point and have many questions such as

    • What is an interrupt?
    • What is a system call?
    • Why do you write your numbers in hexadecimal?
    • What is a register?

    I would probably give you the wrong definition if I had to explain what an interrupt is, from a hardware or software perspective. In this case, the interrupt number 21h is something put into memory by DOS which can be called as if it were a function like you would write in any language.

    The reason the interrupts and other numbers are in hexadecimal is because most assembly language books and tutorials use them. Hexadecimal is objectively more convenient because it can be more easily converted to and from binary. For now just remember that interupt 21h is actually thirty-three and not twenty-one. I have chosen to stick with hexadecimal for this book because it will be relevant later on when I show you other tools which can be used if you understand hexadecimal!

    A register is a special variable that exists on a specific CPU type. DOS, Windows, and most Linux operating systems run on an Intel 8086 compatible CPU. I will explain the registers and their functions.

    The General Purpose Registers

    There are 8 general purpose registers.

    • AX: The Accumulator Register
    • BX: The Base Register
    • CX: The Count Register
    • DX: The Data Register
    • SI: The Source Index
    • DI: the Destination index
    • BP: The Base Pointer
    • SP: The Stack Pointer

    Of those 8 registers, only BX,BP,SI,DI can be used as index variables. This is only a limitation of 16 bit real mode programs like those written in this book. 32-bit and 64-bit programs do not have this limitation, but they have other limitations to worry about and will be covered in future books.

    These registers are “general” in that they can do many things, but they each have more “specific” uses also.

    In my source code, I use lowercase for the names of instructions and registers, but for this section, I listed them in capital letters because they are actually acronyms named for their purpose according to what Intel had in mind when making the 8086 and above Central Processing Units.

    Most of the time, I stick with only AX,BC,CX,DX when writing my programs. If I need an extra registers, I will use BP,SI,DI. There are special instructions for them but these are outside the scope of what I am trying to teach with this book.

    You might wonder, why isn’t there EX,FX,…YX,ZX? Perhaps in a perfect world there should have been, but they probably didn’t want to spend the extra money on having extra registers for every 26 letter of the alphabet.

    In the next chapter I am going to introduce a program that can print any string you give to it. Basically, it will be the proper “Hello World” program that you were expecting.

    Interrupt Information

    All code depends on different functions of interrupt 21h in this book. I have provided the following link to where you can read about the most common functions of this interrupt which will be used in this book

    The function chosen depends on the value of the AH register (the upper half of the AX register). Depending on which function is selected, then other registers act as options or arguments to these functions. The example I included in this chapter shows only the ah=2 call (equivalent of C putchar call) and the exit call of ah=4Ch with al being the return value.

    Chapter 2: The putstring Function

    For this next program, I will be introducing the “putstring” function that I wrote. This function takes the address of wherever the ax register points to, then does a routine to scan for the next zero byte. Then it subtracts the original address from the address where the zero was found. By doing this, it knows how many bytes there are to print in the string.

    Then it loads the registers in the following way:

    • AH = 40h (the DOS write call)
    • BX = file handle
    • CX = number of bytes to write
    • DS:DX -> data to write

    Then interrupt 21h is called and this executes the most fun system call possible. It can print any string you give it. Take the following source and assemble it with either FASM or NASM as described in chapter 1.

    org 100h
    
    main:
    
    mov ax,text
    call putstring
    
    mov ax,4C00h
    int 21h
    
    text db 'Hello World!',0Dh,0Ah,0
    
    ;This section is for the putstring function I wrote.
    ;It will print any zero terminated string that register ax points to
    
    stdout dw 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    

    If you assembled it and ran it in DOS, you should get

    Hello World!
    

    As the result. I know this doesn’t seem very impressive, but this program accomplishes a lot. You see, in Assembly, you don’t have access to C’s “printf” or even “puts”. However, the 40h call of DOS is useful enough that during the course of this book, I will introduce how you can use my functions to replace the standard library output functions or even modify them if you don’t like the way I wrote them!

    If I had to compare DOS 40h to something in C, I would compare it to the “fwrite” function which writes a specified number of bytes to a specific file stream. Writing to file 1 is the same as writing to the screen.

    Specifically, entry “D-2140” in “INTERRUP.F” of Ralf Brown’s Interrupt list is where I got my documentation I required to write the “putstring” function.

    If you look at the source, you will see I included a “main:” label. This wasn’t actually necessary but I added it for clarity and to distinguish the main function from the putstring function. This is a convention I will keep for the remainder of this book.

    The “Hello World!” is defined as data in the assembler like this.

    text db 'Hello World!',0Dh,0Ah,0
    

    That line is not actually assembly language but is pure data according to the way it is defined in both FASM and NASM. The “0Dh,0Ah” are bytes defining the end of a line in DOS. Finally, I ended the string with a 0 because the putstring function uses it to know when to stop printing.

    Therefore, the entire main function is:

    main:
    
    mov ax,text
    call putstring
    
    mov ax,4C00h
    int 21h
    

    The “call” instruction calls a function. As far as assembly is concerned, a function is just a label the same as why you might use for a loop. The difference is that a “ret” intruction will send the program back to where it should be when the function is done. If you forget the “ret” instruction, you will cause a crash because the computer will keep trying to execute code that you did not write. Luckily, if you are running your DOS program in DOSBox, you will only crash the emulator and not your host operating system.

    When I designed the putstring function, I chose ax as the register to first hold the address of the string. I did this because ‘a’ is the first letter of the alphabet and so I use it as the first argument for any of my written functions.

    However, considering that the dx register is used for the data location in the DOS write call, perhaps it would have made more sense to write it that way. This is just a matter of personal taste and I mention it to show you that even assembly language allows a certain amount of personal style when writing your code.

    You may have noticed the push instructions at the beginning of the putstring function and the pop instructions at the end of the function. The push and pop intructions operate the “stack”, It is a First In Last Out method of managing temporary storage.

    Because we are required to use those 4 registers for the system call, we back them up and then restore them. This way, the registers retain their original value as if we had never modified them in the function. This may not seem important now, but in the following chapters, we will be printing lots of strings and numbers, so it is important that their values don’t change while we use them in integer sequence programs later on!

    But all the putstring function does is print a string of text. It can’t print numbers as humans would expect to see them, at least not yet! In the next chapter, I will correct this problem by showing you a function that can print integers!

    If you don’t understand the reason the programs in chapter 1 and 2 work, that’s because I am first establishing a code base which can be used to give you feedback. Without a way of printing output, we have no idea whether our code is correct!

    Chapter 3: The intstr and putint functions

    In this chapter, I will introduce two new functions designed to work with the putstring function from the last chapter. We can already print a string, but this doesn’t work for numbers. Fortunately I have written my own functions which can convert whatever number is in the ax register into a string which can be displayed.

    The source of a complete program is below. Take a good look at it even if you don’t understand it at first because I will be explaining some things about it.

    org 100h
    
    main:
    
    mov ax,text
    call putstring
    
    mov [radix],word 10
    mov [int_width],word 1 
    
    mov ax,1
    
    main_loop:
    call putint
    add ax,ax
    cmp ax,0
    jnz main_loop
    
    mov ax,4C00h
    int 21h
    
    text db 'This program displays numbers!',0Dh,0Ah,0
    
    ;This section is for the putstring function I wrote.
    ;It will print any zero terminated string that register ax points to
    
    stdout dw 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push ax
    push bx
    push cx
    push dx
    
    mov bx,ax                  ;copy ax to bx for use as index register
    
    putstring_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx], byte 0           ;compare this byte with 0
    jz putstring_strlen_end    ;if comparison was zero, jump to loop end because we have found the length
    inc bx                     ;increment bx (add 1)
    jmp putstring_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_strlen_end:
    
    sub bx,ax                  ; sub ax from bx to get the difference for number of bytes
    mov cx,bx                  ; mov bx to cx
    mov dx,ax                  ; dx will have address of string to write
    
    mov ah,40h                 ; select DOS function 40h write 
    mov bx,[stdout]            ; file handle 1=stdout
    int 21h                    ; call the DOS kernel
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    
    ;this is the location in memory where digits are written to by the intstr function
    int_string db 16 dup '?' ;enough bytes to hold maximum size 16-bit binary integer
    ;this is the end of the integer string optional line feed and terminating zero
    ;clever use of this label can change the ending to be a different character when needed 
    int_newline db 0Dh,0Ah,0 ;the proper way to end a line in DOS/Windows
    
    radix dw 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dw 8
    
    intstr:
    
    mov bx,int_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov cx,1
    
    digits_start:
    
    mov dx,0;
    div word [radix]
    cmp dx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add dx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub dx,10
    add dx,'A'
    
    save_digit:
    
    mov [bx],dl
    cmp ax,0
    jz intstr_end
    dec bx
    inc cx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp cx,[int_width]
    jnb end_zeros
    dec bx
    mov [bx],byte '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    
    mov ax,bx ; store string in ax for display later
    
    ret
    
    ;function to print string form of whatever integer is in eax
    ;The radix determines which number base the string form takes.
    ;Anything from 2 to 36 is a valid radix
    ;in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ;this function does not process anything by itself but calls the combination of my other
    ;functions in the order I intended them to be used.
    
    putint: 
    
    push ax
    push bx
    push cx
    push dx
    
    call intstr
    call putstring
    
    pop dx
    pop cx
    pop bx
    pop ax
    
    ret
    

    If you assemble and run this program, you will get the following output.

    This program displays numbers!
    1
    2
    4
    8
    16
    32
    64
    128
    256
    512
    1024
    2048
    4096
    8192
    16384
    32768
    

    The program prints ax, adds ax to itself, and then stops as soon as ax “overflows” by going higher than the 16 bits limit. When this happens, it will become zero. Our jnz means Jump if Not Zero to the main loop.

    If you look at the main function you will see that I set the radix to 10 with a mov instruction, even though it defaulted to 2. This is because most humans are used to decimal, AKA base ten. The base can be changed at any time in the program however you like.

    Another thing you will notices is that the “putint” function does not process anything at all. It simply backs up the registers, calls the intstr function to create a string and then calls putstring to display it. In this example, a newline is automatically added for convenience. In my own code, I usually have this done manually by another small function, but for the putposes of this book, this default behavior is fine.

    The real power of this program is the intstr function and why it works as it does. I will spend the rest of this chapter explaining why it works, why I designed it this way, and why this function is essential for Assembly language programs to make sense at all.

    First, before the function begins, I have defined data using db and dw directives that FASM and NASM both understand.

    int_string db 16 dup '?'
    

    This creates sixteen bytes of question marks. The actual bytes used here don’t matter but I used ‘?’ marks to signal that the data that will go here is unknown when the program starts. The actual digits of the number we convert from the ax register will replace these bytes when we call the intstr function.

    int_newline db 0Dh,0Ah,0
    

    This line takes care of two problems. First, it makes sure that there is a zero byte after the 16 bytes of data from the int_string variable. It also includes the bytes thirteen and ten in hexadecimal notation. This is how newlines are saved if you have used a text editor in DOS or Windows. When you hit the return key it generates both these bytes to define that a line of text has ended. If you were to change the 0Dh to 0, then the program would print the numbers but without separating them with lines or even spaces. Such a thing would make the numbers hard to read. That is why the default behavior is to print a number and end the line for readability. This works for most simple integer sequence programs I will include in this book.

    radix dw 2
    int_width dw 8
    

    These are two variables that define the base/radix of the number string generated and also the “width” AKA how many leading zeroes are used when writing it. The width should be set to one for most programs when decimal integers are expected. However, setting the width to 8 or 16 makes sense for binary integers where seeing the exact bits in their positions lined up is essential.

    The defaults I have chosen include radix 2 (the binary numeral system) and a width of 8 (for seeing 8 bits of a byte). But the defaults are irrelevant for what you need to know. See how the main function in my example program for this chapter overwrites them in the main function.

    intstr:
    

    This is the label defining the start of the intstr function. If this label were not present, then the “call putint” statement would not know what you mean. Also, keep in mind that “intstr” is just an address in memory much like “radix” and “int_width” are addresses that tell where bytes of data are. However, the convention I use is that labels ending with a colon are labels that will be called with the “call” instruction or jumped to with a jmp or j?? instruction. There will be more explanation of conditional jumps in chapter 3.

    mov bx,int_newline-1
    mov cx,1
    

    Before the loop in the intstr function, we set bx equal to the address of the byte before int_newline. This will be the final ‘?’ we defined earlier. The cx register is set to one to signal the number of bytes that will exist in the string. Every number, including 0 and 1 have at least one digit no matter which base you use. The cx register will come into use near the end of the function in its own loop.

    digits_start:
    

    This is a label defining the loop of where the digits are generated in the string.

    mov dx,0;
    div word [radix]
    cmp dx,10
    jb decimal_digit
    jge hexadecimal_digit
    

    dx is set to 0 because this has to be done before the “div” instruction. Otherwise, it will be mistaken as part of the dividend. This is a quirk of the x86 family of CPUs. The div intruction takes one argument, in this a word value from address radix and divides the ax register. If we don’t zero dx, it will use the dx register as an upper 16 bits of the number we are dividing from as well as using ax as the lower 16 bits.

    For a full explanation of this division behavior, see section “2.1.3 Binary arithmetic instructions” in the FASM documentation. Tomasz Grysztar explains it better than I can and his information greatly helped me when trying to figure out why my function wasn’t working.

    After the division, the dx register contains the remainder of the division. The ax register will be whatever it was divided by the radix. Knowing this, we “cmp dx,10” which means compare the dx register with 10. If it is less or below 10, then we know it is a decimal digit in the ranger of 0 to 9. If it is greater than or equal. Based on these conditions, we jump to one of two sections. One handles decimal digits and the other handles hexadecimal digits. Technically bases 2 to 36 are handled by my program as a consequence of the way I wrote it, but I wrote it with the idea that I would be using this function with only 3 different bases.

    • base 2 or binary for my personal enjoyment
    • base 16 or hexadecimal for a short form of binary
    • base 10 or decimal which is what humans know how to read. It will be used mostly in this book
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add dx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub dx,10
    add dx,'A'
    

    These two sections do the math of converting the byte digit into a character in ASCII representation that is printable. In either case, code moves on to the save_digit label after these.

    save_digit:
    
    mov [bx],dl
    cmp ax,0
    jz intstr_end
    dec bx
    inc cx
    jmp digits_start
    
    intstr_end:
    

    This tiny section saves the digit we obtained from this pass of the loop. The dl register is the lower byte of the dx register so we store this character of the digit into the address pointed to by bx.

    Keep in mind that pointers are a primary feature in Assembly Language despite being criticized in C/C++ and excluded entirely from other languages like Java.

    Next, we compare ax with zero. If it is zero, there are no more digits to write and we will end this loop by jumping to “intstr_end”. Otherwise we decrement (subtract 1 from bx) so that it will point to the digit left of the one we saved this time. We also increment cx so that it knows at least one more digit is to be written because the loop will happen again. We unconditionally jump to digits_start to process digits and save them until ax equals zero.

    After ax is zero, we still have one more job to do in this function. The following loop will prefix the string with extra ‘0’ characters while cx is less than the int_width variable. This will be important for those who need the digits lined up to their place values. This is much more important for binary and hexadecimal than it is decimal, but it can still be helpful in decimal as I will show in a later chapter.

    prefix_zeros:
    cmp cx,[int_width]
    jnb end_zeros
    dec bx
    mov [bx],byte '0'
    inc cx
    jmp prefix_zeros
    end_zeros:
    

    There is only one more instruction before we return from this function. We copy the bx register to the ax register because it points to the beginning of the string. This means that my putstring function will accurately display it because it expects ax to contain the address of the string.

    mov ax,bx
    

    Last but not least, we still have to end the function by returning to the caller.

    ret
    

    Before I end this chapter, I want to explain why I chose the register ax as the foundation for the behavior of my Assembly functions. ax is a special register in the sense that multiplication and division instructions use it as the required number we are multiplying or dividing. The Intel architecture treats this register as being more important for this reason.

    But it is not just that, the programmers of DOS decided that the ax register was what decided which function of interrupt 21h would be called.

    Therefore, because others already treated register ax as special, and since ‘a’ is the first letter of the alphabet, I decided that it would be the foundation of all my functions in “chastelib”, my DOS Assembly Standard Library. You are not expected to take my functions as the way things must be done. Once you are done with this book, you may continue learning beyond my skills and may decide that using another register makes more sense than ax.

    I wrote this book to teach Assembly Language as I understand it, not to force my coding practices on you. However, I add these extra details so that other programmers who have experience in Assembly will have answers before they start emailing me: “Chastity, why didn’t you write the function this way! You can save a few bytes if you use instruction ??? instead or you could achieve faster speed if you avoided this jump here.”

    I am letting you know now, I wrote the code for simplicity rather than performance. I use a very limited subset of the instructions available to the Intel 8086 family of CPUs. I firmly believe that all math for programs I want to write can be written using only mov,push,pop,add,sub,mul,div,cmp,jmp (and conditional jumps as well).

    Chapter 4: Chastity’s Intel Assembly Reference

    I use a very small subset of the Intel 8086 family instruction set. This is both because I want to limit it to my small memory (my brain memory, not computer memory) and because I only care about instructions that existed on CPUs at the time of DOS operating systems. Entire video games and operating systems were written either in Assembly or in C programs that were translated to Assembly. Newer CPUs introduced more instructions but I would argue that these were for convenience or higher speed in limited cases.

    For portability, I stick with the instructions I will teach you in this chapter. By portability, I mean portable between as many CPUs of the intel family. Other processors are of course incompatible but they have their own equivalents by different names.

    Important note. All program listings in this chapter assume that you also included the putstring,intstr,and putint functions as shown in chapters 2 and 3. This can be done by including external files or just copy pasting their text after the system exit call from ax=4C00 and interrupt 21h.

    mov

    The mov instruction copies a number from one location to another. In the FASM and NASM assemblers, the instruction always takes the form

    mov destination,source

    Think of it as “destination=source” as you would right in C. For example, in the following program which prints the number 8, we see that most of the required data is set up with mov instructions.

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    
    mov ax,3
    mov bx,5
    add ax,bx
    
    call putint
    
    mov ax,4C00h
    int 21h
    

    That program also contains the call, int, and add instructions to make a program that does something useful. However, mov instructions take up the largest part of any program. Whether you are filling a register with a number, another register, or a memory location, the mov instruction is the way to do it.

    add

    Next to mov, you will see that add is going to be your friend in Assembly a lot. In the previous example, we saw that 3 and 5 were added to make 8. Just like mov, add follows the same rules.

    add destination,source

    • Source is left of Destination and separated by a comma
    • Source and destination can be registers and memory locations
    • Source and Destination cannot both be memory location

    Most intructions that take two arguments follow these same rules. Once you have mastered mov and add, you can handle almost anything in a program because you know the basic rules.

    There is also the “inc” instruction which takes only one item and adds 1 to it. This is just a shorter way of saying “add Destination,1”

    sub

    As its name implies, sub will subtract the Source from the Destination.

    sub destination,source

    Since it follows the same rules as mov and add (starting to see a pattern yet?), subtraction is just as easy as addition.

    Just as “add” has “inc”, “sub” has the “dec” instruction which subtracts 1. Adding or subtracting 1 are probably the most common thing ever done while programming in any language.

    Just as a review of the mov,add,sub instructions, here is a small program to show their effect.

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    
    mov ax,8
    call putint
    add ax,ax
    call putint
    sub ax,4
    call putint
    
    mov ax,4C00h
    int 21h
    

    That program will output the following.

    8
    16
    12
    

    This is because we set ax to 8, then we added ax to itself, and finally we subtracted 4 from ax. Once you think about how easy this is, read on to see how multiplication and division work.

    mul

    The mul instruction is slightly different than The previous instructions. It takes only one operand which must be either a register or memory location. It multiplies ax by the value of this operand. If the value is too large to fit within the ax register, it puts the higher bits into dx.

    div

    The div instruction divides ax by the operand you give it (the divisor). However, division is a tricky operation because not every number divides evenly into another. It is also more complicated by the fact that the dx register is assumed to be the upper half of the bits in the dividend while ax is the lower bits of the dividend.

    I know it sounds complicated but it is easier than I can explain. I can illustrate this with a small program that multipies and divides!

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    
    mov ax,12
    call putint
    mov bx,5
    mul bx
    call putint
    mov bx,8
    mov dx,0
    div bx
    call putint
    mov ax,dx
    call putint
    
    mov ax,4C00h
    int 21h
    

    The output of that program is this:

    12
    60
    7
    4
    

    This is because 12 was multiplied by 5 to get 60. Then we attempted to divide 60 by 8. It goes in only 7 times (which equals 56). This means the remainder is 4, which is stored in the dx register after the division.

    You may also notice in the source above that I set dx to zero before the div instruction. If this is not done, the dx might have mistakenly had another number and been interpreted as part of the dividend.

    I also think some terminology about division is helpful here.

    • Dividend: The number we are dividing from.
    • Divisor: The number we are dividing the dividend by. AKA how many times can we subtract this number from the divisor?
    • Quotient: The result of the division.
    • Remainder: What is left over if the divisor could not divide perfectly into the dividend.

    As much as I love math, I find some of these terms confusing when I try to explain it in English. Let’s face it, I am better at Assembly Language and the C Programming Language than I am with English, but it looks like you’re stuck with me because normal people are not autistic enough to care!


    For a more in depth explanation of the mul and div instructions, I will include those written by Tomasz Grysztar (creator of the FASM assembler) in the official “flat assembler 1.73 Programmer’s Manual”

    mul performs an unsigned multiplication of the operand and the accumulator. If the operand is a byte, the processor multiplies it by the contents of AL and returns the 16-bit result to AH and AL. If the operand is a word, the processor multiplies it by the contents of AX and returns the 32-bit result to DX and AX.

    div performs an unsigned division of the accumulator by the operand. The dividend (the accumulator) is twice the size of the divisor (the operand), the quotient and remainder have the same size as the divisor. If divisor is byte, the dividend is taken from AX register, the quotient is stored in AL and the remainder is stored in AH. If divisor is word, the upper half of dividend is taken from DX, the lower half of dividend is taken from AX, the quotient is stored in AX and the remainder is stored in DX.


    Perhaps you can see that Assembly language is nothing more than a fancy calculator, except better. This is because there is no question which order the operations take place in. There is no need for mnemonics like “Please excuse my dear Aunt Sally” to remind us “Parentheses, Exponents, Multiplication and Division (from left to right), and Addition and Subtraction”.

    There are still two more instructions before we can construct useful programs. In fact, my previous examples have used these already, but now it is time to explain them in depth.

    cmp

    The cmp instruction compares two operands but does not do any math with them. They remain unchanged but modify flags in the processor that allow us to jump based on certain conditions.

    jmp

    The jmp instruction jumps to another location regardless of any conditions. It has a family of other jump instructions that jump only if certain conditions are true. In fact many of them have multiple names for the same operation. For example je and jz both jump if the two numbers compared would be zero if they were subtracted. This would only be true if they are the same.

    Here is a small chart, but it does not cover every alias for these.

    Instruction Meaning
    je/jz jump if equal
    ja jump if above
    jb jump if below
    jne/jnz jump if not equal
    jna jump if not above
    jnb jump if not below

    Aside from those main 6 conditional jumps that I have memorized. There also exists jl(jump if less) and jg(jump if greater). However, they are for signed/negative numbers which I have not covered. Personally I don’t agree with the way negative numbers are represented in computers but I know that understanding the context of signed vs unsigned is important for more complex programs. Once again, I recommend the FASM programmers manual for details that I have excluded for the purpose of keeping this book short.

    The following program can print a message telling you whether ax is less than , equal to, or more than bx. Upon this foundation all the conditional jumps in my programs and functions are based.

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    
    mov ax,5
    mov bx,8
    cmp ax,bx
    jb less
    je same
    ja more
    
    less:
    mov ax,string_less
    jmp end
    same:
    mov ax,string_same
    jmp end
    more:
    mov ax,string_more
    jmp end
    
    end:
    call putstring
    
    mov ax,4C00h
    int 21h
    
    string_less db 'ax is less than bx',0
    string_same db 'ax is the same as bx',0
    string_more db 'ax is more than bx',0
    

    Personally I think that the system of conditional jumps makes a lot of sense. Other programming languages such as BASIC and C have “goto” statements that work like this. For example, if(ax&lt;bx){goto less;}.

    The only thing I have found difficult is remembering which acronym means which condition. However, since I created the chart in this chapter, now I can refer to it and you can too! As long as I keep these main six types of conditions in my head, and am working with unsigned numbers, I can write Almost any assembly program from scratch.

    push/pop

    The push and pop instructions are something you have already seen in my code. The operate on what is called the "stack". Basically, when you push something, it is like pushing a box of cereal onto a shelf at Walmart. The last item pushed is at the front and will be the first item a customer sees. This is what is called a Last In First Out.

    Not only is the stack useful for saving the value of registers temporarily as I do, but without it, it would not be possible to have callable functions. When you call a function with "call", it is the same as a "jmp" to that location except that it pushes the address where the program was before the call. The "ret" instruction returns to the location that called the function and then proceeds to instructions after it.

    The sp register, as I mentioned in chapter 1, is the stack pointer. Every time you push a value, it stores it at the address the stack pointer is pointing to and the subtracts the size of the native word size. For example, this is always 16 bits in the context of DOS programming for 16 bit .com files. This means that you can use it with the other registers to save their value for later.

    In the next chapter, I will show a useful example of the push and pop instructions and explain a little bit more about this.

    Take it slow

    I know I hit you with a lot of information in this chapter, but trust me, I am intentionally leaving out a lot because I don’t want this book to be the size of the Intel® 64 and IA-32 Architectures Software Developer Manuals

    There are hundreds of instructions for Intel machines and yet if you combine the instructions I have described in this chapter with the “call”,“int”, and “ret” instructions required for calling functions for input and output, you will see that it is possible to write almost any program I want with these instructions.

    I am taking what I have learned by reading the Intel Manuals and the API references available for DOS so that you don’t have to spend as much time figuring these things out as I did. What I can tell you though, is that the result was worth it because I have been able to write programs to accomplish tasks faster than my C programs could. At the same time, the Assembly versions took longer to write than the C versions did. This is the price I must pay to have high performing code.

    Also, there are some bitwise instructions by the names of AND,OR,XOR,NOT,SHL,SHR that are sometimes useful for making programs faster and smaller. However, these only make sense in the context of the Binary Numeral System and I suspect that the average reader of this book does not have the 25 years of experience in Binary math that I do.

    I will be explaining more about these operations in a later chapter because they help a lot when trying to optimize programs for size and speed. However they can make programming LOOK complicated and scare away potentially great new programmers who are just trying to learn to apply the 4 regular arithmetic operations of addition, subtraction, multiplication, and division which apply to all number bases.

    Chapter 5: Integer Sequences and Their Application in Learning

    To be a programmer in any language, a person needs more than information. There must exist the motivation of something you want to make. The challenge is that when you are a beginner, it can be easy to get discouraged because you won’t be making anything big or impressive to other people at the start. All you have to work with in most programming languages is displaying text and numbers.

    Later on you can learn to use third party libraries or native APIs for your operating system. However, what I have always disliked is that the internals of how they work are hidden or obfuscated so that you don’t know how they work.

    But if you love math like I do, you will never have a problem testing your ability by writing small programs to print integer sequences. In this chapter, I will be sharing 3 of my favorite sequences.

    If you have the ebook edition of this book, you will be able to click the links above and learn more about these sequences. Either way, I will show you the code that makes printing these sequences easy. even in Assembly Language

    Fibonacci numbers

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    
    mov ax,0
    mov bx,1
    
    Fibonacci:
    
    call putint
    add ax,bx
    push ax
    mov ax,bx
    pop bx
    cmp ax,1000
    jb Fibonacci
    
    mov ax,4C00h
    int 21h
    
    include 'chastelib16.asm'
    

    When executed, that program will output the following sequence

    0
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    89
    144
    233
    377
    610
    987
    

    Just by looking at it, hopefully you see the pattern. Each number is the sum of the previous two numbers. The loop in this program needed to swap the numbers in ax and bx each time before the next add. Technically, there are two other ways I could have achieved it. I could have used ecx as a temporary storage. I also could have used the xchg instruction which does the same thing, but the stack provided me a convenient way of doing it. We only needed to save the ax register before it was overwritten with bx, then we pop into bx the pushed ax from earlier. None of these methods is more correct than any other, but I tend to favor simplicity and therefore am limiting the type of instructions I use. I also think that using a third register or even another memory location is acceptable for something like this, but since the stack is already used for function calls and is an expected part of Assembly, there is no reason not to use it, especially when we only need to save one register.

    Powers of 2

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    mov [int_newline],0
    
    mov cx,0
    
    mov [array],byte 1
    
    powers_of_two:
    
    ;this section prints the digits
    mov bx,[length]
    array_print:
    dec bx
    mov ax,0
    mov al,[array+bx]
    call putint
    cmp bx,0
    jnz array_print
    call putline
    
    ;this section adds the digits
    mov dl,0
    mov bx,0
    array_add:
    mov ax,0
    mov al,[array+bx]
    add al,al
    add al,dl
    mov dl,0
    cmp al,10
    jb less_than_ten
    
    sub al,10
    mov dl,1
    
    less_than_ten:
    mov [array+bx],al
    inc bx
    cmp bx,[length]
    jnz array_add
    
    cmp dl,0
    jz carry_is_zero
    
    mov [array+bx],1
    inc [length]
    
    carry_is_zero:
    
    ;keeps track of how many times the loop has run
    add cx,1
    cmp cx,64
    jna powers_of_two
    
    mov ax,4C00h
    int 21h
    
    length dw 1
    array db 32 dup 0
    
    include 'chastelib16.asm'
    

    Prime Numbers

    org 100h
    main:
    
    mov word [radix],10
    mov word [int_width],1
    mov [int_newline],0
    
    ;the only even prime is 2
    mov ax,2
    call putint
    call putspace
    
    ;fill array with zeros up to length
    mov bx,0
    array_zero:
    mov [array+bx],0
    inc bx
    cmp bx,length
    jb array_zero
    
    ;start by filtering multiples of first odd prime: 3
    mov ax,3
    
    primes:
    
    ;print this number because it is prime
    call putint
    call putspace
    
    mov bx,ax ;mov ax to bx as our array index variable
    mov cx,ax ;mov ax to cx
    add cx,cx ;add cx to itself
    
    sieve:
    mov [array+bx],1 ;mark element as multiple of prime
    add bx,cx ;check only multiples of prime times 2 to exclude even numbers
    cmp bx,length
    jb sieve
    
    ;check odd numbers until we find unused one not marked as multiple of prime
    mov bx,ax
    next_odd:
    add bx,2
    cmp [array+bx],0
    jz prime_found
    cmp bx,length
    jb next_odd
    prime_found:
    
    ;get next prime read to print in ax
    mov ax,bx
    cmp ax,length
    jb primes
    
    mov ax,4C00h
    int 21h
    
    include 'chastelib16.asm'
    
    length=1000
    array rb length
    

    How to use these examples

    My suggestions is that you download the examples in this chapter from my github repository rather than trying to type them by hand or copy past them. That way you can assemble them with FASM and run them in the DOSBox emulator to see how they work.

    These programs can produce long lists of numbers and so I can’t include all the output in this book. You will have to run them to get the full picture of how magnificent they are!

    Chapter 6: The strint Function

    In this chapter, I will only be introducing one program that uses the three previous functions I described in earlier parts of this book but also includes one more important one!

    What this program does is really quite simple, it takes a string of binary integers called “test_int” and converts it into a real integer using a new function called “strint”.

    Below is the source of this program. Take a minute to look it over. Afterwards, I will explain more about the “strint” function and how it interacts with the other three functions. “putstring”,“intstr”,and “putint”.

    The Program

    org 100h
    
    main:
    
    mov ax,main_string
    call putstring
    
    mov word [radix],2 ; choose radix for integer input/output
    mov word [int_width],1
    
    mov ax,test_int
    call strint
    
    mov bx,ax
    
    mov ax,str_bin
    call putstring
    mov ax,bx
    mov word [radix],2
    call putint
    
    mov ax,str_hex
    call putstring
    mov ax,bx
    mov word [radix],16
    call putint
    
    mov ax,str_dec
    call putstring
    mov ax,bx
    mov word [radix],10
    call putint
    
    mov ax,4C00h
    int 21h
    
    main_string db "This is the year I was born",0Dh,0Ah,0
    
    ;test string of integer for input
    test_int db '11111000011',0
    
    str_bin db 'binary: ',0
    str_hex db 'hexadecimal: ',0
    str_dec db 'decimal: ',0
    
    include 'chastelib16.asm'
    

    For a quick review, the 3 previous function do the following.

    • putstring: prints a zero terminated string pointed to by ax register
    • intstr: converts the integer in ax register into a zero terminated string and then points ax to that string for compatibility with putstring
    • putint: calls intstr and then putstring to display whatever number ax equals

    And now I introduce to you the final function of my 4 function library that I call “chastelib”.

    This function is called “strint” and its importance cannot be overstated. It does the opposite of the “intstr” function. Instead of converting an integer to a string, it does the opposite and takes the string pointed to by ax and converts it into a number returned in the ax register. Much like “intstr”, it uses the global [radix] variable to know which base is being used.

    But the very nature of turning a string into an integer is more complicated by necessity. Any valid 16 bit number can be turned into a string from bases 2 to 36 by the “intstr” function. However, strings can contain characters that are invalid to be converted as numbers. There is also the issue that both capital and lowercase letters might be used in radixes higher than ten. In the program above, I used a binary integer string for an example, but in real applications, such as a famous program I wrote called “chastehex”, it is necessary to write a function that can gracefully handle not only the decimal digits ‘0’ to ‘9’ but also handle letters ‘A’ to ‘Z’ or ‘a’ to ‘z’.

    Below is the full source code of the strint function as written for 16 bit DOS Assembly programs. I spent more time writing this function than the three previous functions combined, but it was necessary for the programs I intended to write! Comments are included although more explanation may be required for some to understand it.

    The Function

    ;this function converts a string pointed to by ax into an integer returned in ax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov bx,ax ;copy string address from ax to bx because ax will be replaced soon!
    mov ax,0
    
    read_strint:
    mov cx,0 ; zero cx so only lower 8 bits are used
    mov cl,[bx] ;copy byte/character at address bx to cl register (lowest part of cx)
    inc bx ;increment bx to be ready for next character
    cmp cl,0 ; compare this byte with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a decimal digit, but it could be perhaps an alphabet character
    ;which could be a digit in a higher base like hexadecimal
    ;we will check for that possibility next
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp cx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov dx,0 ;zero dx because it is used in mul sometimes
    mul word [radix]    ;mul ax with radix
    add ax,cx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    

    If you run the program at the top of this chapter (remember the source is available on github but also can be pieced together from this book alone), it will produce this output.

    The Output

    This is the year I was born
    binary: 11111000011
    hexadecimal: 7C3
    decimal: 1987
    

    These three forms of displaying the same number are quite obviously the most useful radixes that programmers must learn.

    Binary is what computers understand. Without knowing that everything is bits of only 0 or 1, very little about computers makes sense without a complete understanding of the Binary Numeral System.

    Hexadecimal is the short form of Binary because every four bits equals one digit in Hexadecimal. For this reason, hex editors for editing binary files are much more common than binary editors. It just takes less typing and disk space.

    Decimal actually has no value other than what humans have placed on it. Base ten is not special, and computers don’t understand it as well as Binary, but humans expect numbers to be in this form, and by extension, all the video games also display numbers in this form.

    If I told people that I was born in 11111000011, they will think I am really old and should be dead by now. If I tell them I was born in 1987, they will know that I am 38 years old at the time I am writing this book. However, both numbers mean the exact same thing.

    The reason I mention this is that I want you to research different bases/radixes of number systems because it will make you a better programmer. In fact there is a really good book written by another author that I will recommend.

    Binary, Octal and Hexadecimal for Programming & Computer Science by Sunil Tanna

    To be written:

    • Chapter 7: Translating Assembly to Other Programming Languages
    • Chapter 8: Going from DOS to Linux or Windows
    • Chapter 9: Bitwise Operations for Advanced Nerds
    • Chapter 10: chastehex: Not just a program, but a philosophy.

    Chapter Z: More Documentation

    Below is a list of the sources I referenced the most while writing this book. I respect the work of Ralf Brown and any other people involved in keeping DOS programming information available.

    However, as time goes on, DOS information will become harder to find because old people die and can no longer pay to keep their websites online. This book was my attempt at keeping the information alive as long as I live. I have downloaded as much information onto my computer and have old books that are out of print. The time may come when I am the last person on earth who even knows or cares about the old way of programming in DOS.

    And when I die, my only hope is that there is another young autistic programmer who will read my books about computer programming and Chess. May they be inspired to carry on the work of nerdy activities that most will never understand and criticize them for.

    If at any time, something I wrote in this book is unclear to you, please email me to help me explain it better for you in future updates to this and other books.

    chastitywhiterose@gmail.com

    This book is not complete! It will be updated as I find time! Please leave questions, comments, and suggestions on what you would like to see included!

    I am not rushing this book, but I would like to achieve writing this DOS book before I turn 40 and it will be my contribution back to the Open Source community who has helped me in ways that cannot be measured for my whole life. Free Software is the only reason I was able to learn programming and write all my books.

  • The Bitwise Operations

    There are 5 bitwise operations which operate on the bits of data in a computer. For the purpose of demonstration, it doesn’t matter which number the bits represent at the moment. This is because the bits don’t have to represent numbers at all but can represent anything described in two states. Bits are commonly used to represent statements that are true or false. For the purposes of this section, the words AND, OR, XOR are in capital letters because their meaning is only loosely related to the Englist words they get their name from.

    Bitwise AND Operation

    0 AND 0 == 0
    0 AND 1 == 0	
    1 AND 0 == 0
    1 AND 1 == 1
    

    Think of the bitwise AND operation as multiplication of single bits. 1 times 1 is always 1 but 0 times anything is always 0. That’s how I personally think of it. I guess you could say that something is true only if two conditions are true. For example, if I go to Walmart AND do my job then it is true that I get paid.

    Bitwise OR Operation

    0 OR 0 == 0
    0 OR 1 == 1	
    1 OR 0 == 1
    1 OR 1 == 1
    

    The bitwise OR operation can be thought of as something that is true if one or two conditions are true. For example, it is true that playing in the street will result in you dying because you got run over by a car. It is also true that if you live long enough, something else will kill you. Therefore, the bit of your impending death is always 1.

    Bitwise XOR Operation

    0 XOR 0 == 0
    0 XOR 1 == 1	
    1 XOR 0 == 1
    1 XOR 1 == 0
    

    The bitwise XOR operation is different because it isn’t really used much for evaluating true or false. Instead, it is commonly used to invert a bit. For example, if you go back to the source of my graphics programs in Chapter 2, you will see that most of those programs contain the statement:

    index^=1;

    If you look at my XOR chart above, you will see that using XOR of any bit with a 1 causes the result to be the opposite of the original bit. In the context of those programs, the index variable is meant to be 0 to represent black and 1 to represent white. The XOR operation is the quickest way to achieve this bit inversion. In fact, in all my years of programming, that’s pretty much the only thing I have used it for!

    Bitwise Left and Right Shift Operations

    Consider the case of the following 8 bit value:

    00001000

    This would of course represent the number 8 because a 1 is in the 8’s place value. We can left shift or right shift.

    00001000 ==  8 : is the original byte
    
    00010000 == 16 : after left shift
    00000100 ==  4 : after right shift
    

    Left and right shift operations allow us to multiply or divide a number by 2 by taking advantage of the base 2 system. These shifts are essential in graphics programming because sometimes to need to extract the red, green, or blue values separately out of their 24 bit representation. For example, consider this code:

       pixel=p[x+y*width];
       r=(pixel&0xFF0000)>>16;
       g=(pixel&0x00FF00)>>8;
       b=(pixel&0x0000FF);
    

    The first statement gets the pixel out of an array of data which is indexed by x and y geometric coordinates. This will be a 24 bit value, or in some cases 32 bit with the highest 8 bits representing the alpha or transparency level.

    variables r,g,b represent red, green, and blue. With clever use of bitwise AND operations and right shifting by the correct number of bits, it is possible to extract just that color component to be modified. Without the ability to do this, my graphics animations and my Tetris game would never have been possible. The colors had to be exactly sent to the drawing functions. This is true not just for SDL but using any graphical system involving colors.

    Learning More

    I know I covered a lot in this chapter but I encourage you to learn about the binary numeral system and its close cousin the hexadecimal system. If you do an online search, you will find courses, tutorials, and videos by millions of people who can probably explain these same concepts in a way that you understand better if you are still confused after reading this chapter!

  • Chastity’s Code Cookbook

    Computer Programming Recipes for Technical Math Nerds

    Chastity White Rose

    Preface

    You would not know it by looking at me, but I have been computer programming as a hobby since I was 14 years old. My first programming language was QBASIC. It was a language and a program for interpreting that language that ran on MS-DOS. However, the usage of this language has diminished in use over time because it does not run without an emulator or modern BASIC language dialects meant to mimic it.

    However, I moved to the C Programming Language as my main computer language. I have dabbled in Java, Javascript, Lua, Perl, Python, and the beast known as C++. However, of all these languages, C remains my native language because of how simple it is to remember. In spite of its quirks, C is what I recommend to a beginner not in spite of its limitations but because of them! Therefore, most of the recipes in this code cookbook will be in the form of C source code.

    But this book will contain more than just the C language. There are times when other languages such as Bash, HTML, Markdown, and maybe even Lua or Python will just make a lot more sense in the context of what is being done.

    For example, computer programming is used to create art, web pages, books, and video games. However, programming is also hard work and a lonely pursuit because almost nobody understands it except those of us who are called to this sacrificial art of communicating with a computer.

    That being said, my computer understands what I mean better than most humans do. The purpose of this book is to archive some of the best programs I have written, and yet at the same time, are simple enough to share in only one or two pages.

    This project began as an effort to save all my work so that it is not lost. I also hope that new generations of computer programmers can learn something from my 20+ years of experience as a C programmer.

    Introduction

    I got my start in the world of computer programming because I first loved numbers. I am the stereotype of an autistic savant who sees everything as a number. If you like numbers, my hope is that my recipes for generating number patterns will be of some use to you.

    And if you don’t like numbers, then you probably won’t do well as a computer programmer because everything is a number in the context of a computer. The size, shape, and color of every text or picture element in the video games you play were at one point written into the code by one or more programmers who probably were not paid enough for the work that went into their craft!

    But before I begin sharing my code recipes with you, there is something I need to do before you can fully enjoy the experience. You will want to install a C compiler on your computer!

    If you are using Debian or Ubuntu Linux, installing the GCC compiler is as simple as sudo apt install gcc. However, I expect that most of my readers have a computer with the Windows operating system installed since the computer was purchased.

    Don’t worry, you can still follow along! When I bought my Windows 11 laptop, I set up scoop to be my command line installer. Then I installed gcc so that I can always have it available from the command line on Windows just like I could in Linux.

    scoop install gcc

    There are other ways to install GCC on Windows. However, all of them will be giving the the result of having the ability to type gcc into the terminal or console to compile and run your C code.

    Once it is installed correctly, you can enter gcc and get the message:

    gcc: fatal error: no input files
    compilation terminated.
    

    But that is okay! We are going to give it an input file to compile! Type the following into a text file named main.c.

    #include <stdio.h>
    int main()
    {
     printf("Hello, World!\n");
     return 0;
    }
    
    

    You can compile this and run from the command line with:

    gcc main.c -o main && main

    If done correctly while at a command line in the same folder as the source file, you will get the message:

    Hello, World!

    If you see this, then it means that the program compiled and ran successfully.

    You see, the command gcc main.c -o main && main is actually two commands in one. The first part

    gcc main.c -o main

    tells the compiler to process it and create an executable file. The second part

    main

    Tells it to run the executable file. On Windows, this would run a program named “main.exe”. On Linux, the file is likely to be named simply “main”. Also, on Linux, you would need to write it as “./main” to execute it. This signals that the file you are trying to run is in the current directory.

    How it works

    Now that you have successfully ran the Hello World program, you might be wondering what it all means. Here is the same program with comments included.

    #include <stdio.h> /*include the standard input and output library*/
    int main() /*beginning of a function named main which returns an integer*/
    { /*opening bracket starting the function block*/
     printf("Hello, World!\n"); /*calling printf, the most useful function in the C Programming Language.*/
     return 0; /*Return the number zero to the operating system. This means no errors occurred.*/
    } /*closing bracket ending the function block*/
    
    

    Once you can get this program working, then the rest of the C source code samples in this book will work fine! They all use elements of the C Standard Library. The file “stdio.h” is one of the library files.

    I will do my best to explain the usage of functions as they are introduced. However, for more information, it is better to go to an online reference such as cppreference.com for more information than I have included.

    For example, you can find on there the usage of the printf family of functions. Arguably, printf is the most used function in C because of its ability to output strings of text and numbers to display any useful information to the user of the program. Without output, you would have no way of knowing whether your code was correct.

    I do not use all of the functions in the standard library. In fact, I would say that most of the functions and features of the C language I have never learned because I never needed to. The reason for this will become more clear in Chapter 1 when I cover what I do best: writing code to generate integer sequences.

    Chapter 1: Integer Sequences

    An integer is a whole number. For example, 1,2,3,4,5 etc are all integers. Negative numbers like -15 or -23 are also integers.

    Things that are not an integer include anything that comes with a decimal point. For example, Pi:

    3.14159265358979323846

    Or the Square root of 2.

    1.41421356237309504880

    Integers, specifically positive integers are the focus of this chapter. I will start with a code sample that generates all the integers up to 100.

    Count 100

    #include <stdio.h>
    int main()
    {
     int a=0;
     while(a<=100)
     {
      printf("%d ",a);
      a++;
     }
     return 0;
    }
    

    If done correctly, you will see the following in your terminal.


    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100


    How this works is very simple. with int a=0; We define an integer named a that starts at 0. Then we set up a while conditional block defined by while(a<=100) which means that the following block will happen as long as a is less than or equal to 100.

    Just like the main function, each block begins with a { and ends with a }. This is common for languages like C, C++, Java, and Javascript.

    There are two statements in this block. The first is:

    printf("%d ",a);

    Says that we are going to print a decimal integer followed by a space. The second argument (or option) to the printf function is the variable ‘a’.

    The next statement:

    a++;

    Is a short way of saying “add 1 to a”. You could just as easily write “a=a+1;” or “a+=1;”

    Upon this foundation, all other integer sequence code is written. There are always these steps.

    • Define the variables used
    • Set up the looping conditionals
    • One or more statements defining the math

    One important misunderstanding about computer programming is that some people think it is related to algebra. Not in the slightest. I failed at algebra more times than I can count but this is not like that. As a computer programmer, I get to say what the variables are and how they change. Algebra is different because you are trying to discover what someone else decided a,b,c or x,y,z are. Where is the fun in that?!

    But computer programming can be fun if you know how to write it. As you look at my next samples, you may begin to understand why I enjoy it.

    The Powers of 2

    The powers of 2 are an interesting sequence because of their application in computer science (computers work in the binary numeral system, not decimal) as well as biology. It is extremely easy to write a basic program to generate the sequence of the powers of 2.

    #include <stdio.h>
    int main()
    {
     int a=0,b=32,c=1;
     while(a<=b)
     {
      printf("2 ^ %2d = %11d\n",a,c);
      a++;
      c+=c;
     }
     return 0;
    }
    

    This short program generates the powers of two sequence. It starts at 1 and doubles the number each time by adding it to itself. The output is as follows.

    2 ^  0 =           1
    2 ^  1 =           2
    2 ^  2 =           4
    2 ^  3 =           8
    2 ^  4 =          16
    2 ^  5 =          32
    2 ^  6 =          64
    2 ^  7 =         128
    2 ^  8 =         256
    2 ^  9 =         512
    2 ^ 10 =        1024
    2 ^ 11 =        2048
    2 ^ 12 =        4096
    2 ^ 13 =        8192
    2 ^ 14 =       16384
    2 ^ 15 =       32768
    2 ^ 16 =       65536
    2 ^ 17 =      131072
    2 ^ 18 =      262144
    2 ^ 19 =      524288
    2 ^ 20 =     1048576
    2 ^ 21 =     2097152
    2 ^ 22 =     4194304
    2 ^ 23 =     8388608
    2 ^ 24 =    16777216
    2 ^ 25 =    33554432
    2 ^ 26 =    67108864
    2 ^ 27 =   134217728
    2 ^ 28 =   268435456
    2 ^ 29 =   536870912
    2 ^ 30 =  1073741824
    2 ^ 31 = -2147483648
    2 ^ 32 =           0
    

    Everything looks nice here. I even aligned the digits of the output to make it beautiful. It is all fine until we get 2 to the 31st power. Why is it negative?! Why is 2^32 now 0?

    I understand it because I know how integer sizes work on computers. An int type in the C language is usually a 32 bit integer and so it can only contain 32 bits (binary digits) which represent the sum of powers of two that can represent any integer up to that limit. However, because numbers are infinite, at some point data is lost and we end up back at zero.

    For a complete understanding on the topic of the binary numeral system, I would probably have to write a whole book on it as well. However, for now, I will show you that through clever code writing, it is possible to break this limit and go far beyond this wall that the last program runs into.

    The following program may scare you a little bit. It uses an array of decimal digits to represent a number instead of using the built in machine integers. However messy it looks, it certainly does the job.

    Advanced Powers of 2

    #include <stdio.h>
    int main()
    {
     int a=0,b=64;
    
     int x,y;
     #define length 1000
     int length2=20;
     char c[length];
    
     x=0;
     while(x<length)
     {
      c[x]=0;
      x++;
     }
     c[0]=1;
    
     while(a<=b)
     {
      printf("2 ^ %2d = ",a);
      a++;
    
      x=length2;
      while(x>0)
      {
       x--;
       printf("%d",c[x]);
      }
      printf("\n");
    
      y=0;
      x=0;
      while(x<=length2)
      {
       c[x]+=c[x];
       c[x]+=y;
       if(c[x]>9){y=1;c[x]-=10;}else{y=0;}
       x++;
      }
      if(c[length2]>0){length2++;}
    
     }
     return 0;
    }
    

    The output is the following:

    2 ^  0 = 00000000000000000001
    2 ^  1 = 00000000000000000002
    2 ^  2 = 00000000000000000004
    2 ^  3 = 00000000000000000008
    2 ^  4 = 00000000000000000016
    2 ^  5 = 00000000000000000032
    2 ^  6 = 00000000000000000064
    2 ^  7 = 00000000000000000128
    2 ^  8 = 00000000000000000256
    2 ^  9 = 00000000000000000512
    2 ^ 10 = 00000000000000001024
    2 ^ 11 = 00000000000000002048
    2 ^ 12 = 00000000000000004096
    2 ^ 13 = 00000000000000008192
    2 ^ 14 = 00000000000000016384
    2 ^ 15 = 00000000000000032768
    2 ^ 16 = 00000000000000065536
    2 ^ 17 = 00000000000000131072
    2 ^ 18 = 00000000000000262144
    2 ^ 19 = 00000000000000524288
    2 ^ 20 = 00000000000001048576
    2 ^ 21 = 00000000000002097152
    2 ^ 22 = 00000000000004194304
    2 ^ 23 = 00000000000008388608
    2 ^ 24 = 00000000000016777216
    2 ^ 25 = 00000000000033554432
    2 ^ 26 = 00000000000067108864
    2 ^ 27 = 00000000000134217728
    2 ^ 28 = 00000000000268435456
    2 ^ 29 = 00000000000536870912
    2 ^ 30 = 00000000001073741824
    2 ^ 31 = 00000000002147483648
    2 ^ 32 = 00000000004294967296
    2 ^ 33 = 00000000008589934592
    2 ^ 34 = 00000000017179869184
    2 ^ 35 = 00000000034359738368
    2 ^ 36 = 00000000068719476736
    2 ^ 37 = 00000000137438953472
    2 ^ 38 = 00000000274877906944
    2 ^ 39 = 00000000549755813888
    2 ^ 40 = 00000001099511627776
    2 ^ 41 = 00000002199023255552
    2 ^ 42 = 00000004398046511104
    2 ^ 43 = 00000008796093022208
    2 ^ 44 = 00000017592186044416
    2 ^ 45 = 00000035184372088832
    2 ^ 46 = 00000070368744177664
    2 ^ 47 = 00000140737488355328
    2 ^ 48 = 00000281474976710656
    2 ^ 49 = 00000562949953421312
    2 ^ 50 = 00001125899906842624
    2 ^ 51 = 00002251799813685248
    2 ^ 52 = 00004503599627370496
    2 ^ 53 = 00009007199254740992
    2 ^ 54 = 00018014398509481984
    2 ^ 55 = 00036028797018963968
    2 ^ 56 = 00072057594037927936
    2 ^ 57 = 00144115188075855872
    2 ^ 58 = 00288230376151711744
    2 ^ 59 = 00576460752303423488
    2 ^ 60 = 01152921504606846976
    2 ^ 61 = 02305843009213693952
    2 ^ 62 = 04611686018427387904
    2 ^ 63 = 09223372036854775808
    2 ^ 64 = 18446744073709551616
    

    The concept of an array is really easy to explain. An array is a list of numbers. In the program above, variable ‘c’ is not one number but a list of 1000 numbers. With #define length 1000 we declared a compiler constant representing the maximum length the array can be. With int length2=20; we declared the length of the number of digits we want printed. Finally, with char c[length]; we declared the array of integers named ‘c’ rather than just one integer.

    If you are wondering why ‘c’ is defined as type “char” instead of “int”, that is just to save memory. A char is an 8 bit integer instead of the usual 32 bit. For our purpose in this program, this is more than enough.

    The first loop in this code initializes the entire array with zeros. The x variable is used only as an index variable but has no special meaning on its own.

     x=0;
     while(x<length)
     {
      c[x]=0;
      x++;
     }
     c[0]=1;
    

    Literally it is setting everything from c[0] to c[999] to 0. Then it sets c[0] equal to 1; This lowest element represents the ones place. Elements 1,2,3 and so on represent the tens, hundred, thousands, etc.

    Most parts of this program are still the same as the simple one. Variables ‘a’ and ‘b’ control how many times the big loop goes.

      x=length2;
      while(x>0)
      {
       x--;
       printf("%d",c[x]);
      }
      printf("\n");
    

    By starting at length2, we decide at what point to begin printing the digits. Each time, ‘x’ is decreased by 1 and we print the current element represented by c[x].

    And finally, the real magic happens in this part.

      y=0;
      x=0;
      while(x<=length2)
      {
       c[x]+=c[x];
       c[x]+=y;
       if(c[x]>9){y=1;c[x]-=10;}else{y=0;}
       x++;
      }
      if(c[length2]>0){length2++;}
    

    Variable ‘y’ is used as a “carry” in the addition process. We are starting at the bottom of the array and adding each digit to itself. Then we add the carry variable to the current digit.

    Then, we have an “if statement” to execute if the digit goes higher than 9. If this happens, we subtract ten from it and then set the carry to 1. Otherwise, we set the carry back 0 zero. Finally at the end of this loop is a conditional that automatically increases the length 2 variable if the carry has caused it to be higher than zero. This makes an automatically expanding list of decimal digits.

    As you might guess, I spent many hours perfecting this powers of two program. I wanted it fast but I also tried to make it clear and readable. For an experienced programmer, this is nothing, but it is the kind of code that makes non-programmers confused and they think I am a genius.

    And maybe I am, but the point is that I only instructed the computer to do what I already know how to do on paper and in my mind. The same process of addition applies when adding a deposit to a check register for example.

    The Prime Numbers

    The prime numbers are a fun study for many people. The definition of a prime number is that it has exactly two factors (the number itself and 1). For example 7 is a prime number because it is not divisible by anything else other than 1. 9 is not prime because 3 times 3 equals 9.

    #include <stdio.h>
    int main()
    {
     int x,y;
     #define length 1000
     char c[length];
    
     x=0;
     while(x<length)
     {
      c[x]=0;
      x++;
     }
     c[0]=1;
    
     printf("2 ");
     x=3;
     while(x<length)
     {
      printf("%d ",x);
      y=x;
      while(y<length)
      {
       c[y]=1;
       y+=x;
      }
      while(x<length && c[x]>0){x+=2;}
     }
     
     return 0;
    }
    
    

    This program is relatively short considering that it finds all the prime numbers less than 1000 very efficiently.


    2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997


    Like my powers of two program, it uses an array named ‘c’. However, this time we are using the array as indexes in a prime finding process called the Sieve of Eratosthenes.

    We are simply going through the array each time a prime number is found and then marking each index as a multiple of that number. Afterwards, the index goes to the next element which is still a zero, meaning that no factors have been found for this number.

    Conclusion

    The examples I have added in this chapter have shown the required elements of writing effective code. Knowing how to set integer variables (or arrays of them) is required for generating integer sequences. I can also add programs to this chapter for other sequences upon request if anyone has a sequence that they think is worth including.

    Chapter 2: Graphics

    While most of the focus of this book is about generating text information, such as the integer sequences of Chapter 1, there is a lot more potential that you may not realize at first. If we consider that all programming, scripting, and markup languages are created using text, it leads to the idea that we can use C in combination with other languages to generate pictures!

    For my next example, I will be using SVG which stands for Scalable Vector Graphics. For more information on it, I highly recommend reading the specification.

    SVG Checkerboard

    #include <stdio.h>
    int main()
    {
     int width=720,height=720;
     int x=0,y=0;
     int index=0,index1=0;
     int rect_width=90,rect_height=90;
    
     printf("<svg width=\"%d\" height=\"%d\">\n",width,height);
    
     printf("<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"fill:#FFFFFF;\" />\n",x,y,width,height);
    
     y=0;
     while(y<height)
     {
      index1=index;
      x=0;
      while(x<width)
      {
       if(index==1)
       {
        printf("<rect x=\"%d\" y=\"%d\" width=\"%d\" height=\"%d\" style=\"fill:#000000;\" />\n",x,y,rect_width,rect_height);
       }
       index^=1;
       x+=rect_width;
      }
      index=index1^1;
      y+=rect_height;
     }
    
     printf("</svg>\n");
    
     return 0;
    }
    
    

    The program produces the following code

    <svg width="720" height="720">
    <rect x="0" y="0" width="720" height="720" style="fill:#FFFFFF;" />
    <rect x="90" y="0" width="90" height="90" style="fill:#000000;" />
    <rect x="270" y="0" width="90" height="90" style="fill:#000000;" />
    <rect x="450" y="0" width="90" height="90" style="fill:#000000;" />
    <rect x="630" y="0" width="90" height="90" style="fill:#000000;" />
    <rect x="0" y="90" width="90" height="90" style="fill:#000000;" />
    <rect x="180" y="90" width="90" height="90" style="fill:#000000;" />
    <rect x="360" y="90" width="90" height="90" style="fill:#000000;" />
    <rect x="540" y="90" width="90" height="90" style="fill:#000000;" />
    <rect x="90" y="180" width="90" height="90" style="fill:#000000;" />
    <rect x="270" y="180" width="90" height="90" style="fill:#000000;" />
    <rect x="450" y="180" width="90" height="90" style="fill:#000000;" />
    <rect x="630" y="180" width="90" height="90" style="fill:#000000;" />
    <rect x="0" y="270" width="90" height="90" style="fill:#000000;" />
    <rect x="180" y="270" width="90" height="90" style="fill:#000000;" />
    <rect x="360" y="270" width="90" height="90" style="fill:#000000;" />
    <rect x="540" y="270" width="90" height="90" style="fill:#000000;" />
    <rect x="90" y="360" width="90" height="90" style="fill:#000000;" />
    <rect x="270" y="360" width="90" height="90" style="fill:#000000;" />
    <rect x="450" y="360" width="90" height="90" style="fill:#000000;" />
    <rect x="630" y="360" width="90" height="90" style="fill:#000000;" />
    <rect x="0" y="450" width="90" height="90" style="fill:#000000;" />
    <rect x="180" y="450" width="90" height="90" style="fill:#000000;" />
    <rect x="360" y="450" width="90" height="90" style="fill:#000000;" />
    <rect x="540" y="450" width="90" height="90" style="fill:#000000;" />
    <rect x="90" y="540" width="90" height="90" style="fill:#000000;" />
    <rect x="270" y="540" width="90" height="90" style="fill:#000000;" />
    <rect x="450" y="540" width="90" height="90" style="fill:#000000;" />
    <rect x="630" y="540" width="90" height="90" style="fill:#000000;" />
    <rect x="0" y="630" width="90" height="90" style="fill:#000000;" />
    <rect x="180" y="630" width="90" height="90" style="fill:#000000;" />
    <rect x="360" y="630" width="90" height="90" style="fill:#000000;" />
    <rect x="540" y="630" width="90" height="90" style="fill:#000000;" />
    </svg>
    

    There is no mystery as to why it works the way it does. The location, size, and color of each square is a number. If everything is a number and numbers are represented by a text language like SVG to show pictures in a web browser, then we can create pictures with something as simple as printf statements in the C programming language. Of course, you usually have to copy and paste the text into a file and save it with the “.svg” extension for most programs to be able to read it.

    The next method I will be using to generate a picture does not use vector graphics at all. Instead, it operates a large array of 32 bit integers as if they were pixels. The following program calls several different functions that I wrote as part of my BBM (Binary Bit Map) project.

    Portable Bitmap Checkerboard

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdint.h>
    #include "bbm.h"
    int main()
    {
     uint32_t *p=NULL; /*The pointer to the pixels*/
     int width=720,height=720; /*The size of the image.*/
     int square_size=90; /*size of each square in the checkerboard that will be created*/
     uint32_t colors[]={0x000000,0xFFFFFF};
    
     p=BBM_malloc(width,height);
    
     chastity_checker(p,width,height,square_size,colors[1],colors[0]);
    
     BBM_SavePBM(p,width,height,"image.pbm");
    
     BBM_free(p);
    
     return 0;
    }
    

    If you take a look at the source, you will see that it includes a file named “bbm.h”. This is a small file that I wrote with some utility functions. The BBM project was designed to be an entire library that created pictures of black and white by writing to image files.

    To properly run this code, I will also included the source below to the “bbm.h” file and all the functions which are called in the main function from above. If you copy this to a file named “bbm.h” and place it in the same folder as your main source file, it will work correctly.

    Don’t worry if you don’t understand it all right now, it took me months of work to get the formulas for these just right.

    /*
    # Binary Bit Map
    
    This C library file was created for my book, Chastity's Code Cookbook. It demonstrates that it is possible to create image files with only the C standard library.
    All code was written entirely with by Chastity White Rose. The design is focused on black and white images only, but the format used for pixels is 32 bit unsigned integers.
    This code is therefore extendable to use for any colors if I wish to expand it.
    */
    
    /*
     Allocates memory for the pixels which should be 4 bytes/32 bits per pixel. Uses standard library function malloc.
     uint32_t is a 32 bit unsigned integer type. This is why stdint.h is always included.
     I never need more than 32 bits and any more would waste memory.
    */
    uint32_t* BBM_malloc(uint32_t width,uint32_t height)
    {
     uint32_t *pointer;
     int length=width*height;
     pointer=(uint32_t*)malloc(length*sizeof(*pointer));
     if(pointer==NULL){printf("Error: malloc failed,\n");}
     return pointer;
    }
    
    /*
    frees the memory the pointer points to, but only if the pointer is not already NULL.
    */
    void BBM_free(uint32_t *pointer)
    {
     if(pointer!=NULL){free(pointer);pointer=NULL;}
    }
    
    /*
     The function that saves the pixels to a PBM file.
     0 is black and 1 is White.
     Each byte contains 8 pixels. One per bit.
    */
    void BBM_SavePBM_Pixels(uint32_t *p,uint32_t width,uint32_t height,FILE* fp)
    {
     uint32_t x,y,pixel,r,g,b,gray,bitcount,bits,bpp=1;
    
     y=0;
     while(y<height)
     {
      bitcount=0;
      bits=0;
      x=0;
      while(x<width)
      {
       pixel=p[x+y*width];
       r=(pixel&0xFF0000)>>16;
       g=(pixel&0x00FF00)>>8;
       b=(pixel&0x0000FF);
       gray=(r+g+b)/3;
       gray>>=8-bpp; gray^=1;
       bits<<=bpp;
       bits|=gray;
       bitcount+=bpp;
       x++;
       while(bitcount>=8)
       {
        fputc(bits,fp);
        bitcount-=8;
       }
      }
    
      /*If width is not a multiple of 8 pad the bits to a full byte*/
      while(bitcount!=0)
      {
       bits<<=1;
       bitcount++;
       if(bitcount==8)
       {
        fputc(bits,fp);
        bitcount=0;
       }
      }
      y++;
     }
    
    }
    
    /*
    Saves to PBM. My favorite already existing format because of it's simplicity. Next to my own BBM format this is the most efficient uncompressed storage of black and white pixels I have seen, unless there is another format I don't know about.
    */
    void BBM_SavePBM(uint32_t *p,uint32_t width,uint32_t height,const char* filename)
    {
     FILE* fp;
     fp=fopen(filename,"wb+");
     if(fp==NULL){printf("Failed to create file \"%s\".\n",filename); return;}
     else{/*printf("File \"%s\" opened.\n",filename);*/}
     fprintf(fp,"P4\n"); fprintf(fp,"%d %d\n",width,height);
    
     BBM_SavePBM_Pixels(p,width,height,fp);
    
     fclose(fp);
     /*printf("Saved to file: %s\n",filename);*/
    }
    
    /*
    Code for filling the image with a checkerboard. This is my most precious of programming creations!
    */
    void chastity_checker(uint32_t *p,uint32_t width,uint32_t height,uint32_t square_size,uint32_t color0,uint32_t color1)
    {
     uint32_t x,y=0,index=0,index1,bitcountx,bitcounty=0;
     while(y<height)
     {
      index1=index;
      bitcountx=0;
      x=0;
      while(x<width)
      {
       if(index==0){p[x+y*width]=color0;}
        else       {p[x+y*width]=color1;}
       bitcountx+=1;if(bitcountx==square_size){bitcountx=0;index^=1;}
       x+=1;
      }
      index=index1;
      bitcounty+=1;if(bitcounty==square_size){bitcounty=0;index^=1;}
      y+=1;
     }
    }
    

    The image file is a PBM (Portable Bit Map). It is a very simple but also well documented format. I learned this format so that I can write an array of pixels to a binary file.

    Let me briefly explain the function of the various functions.

    BBM_malloc allocates memory for the amount of pixels we need. It calls malloc, which is a C standard library function. However, I wrote the functiont automatically print an error message if memory allocation fails.

    BBM_free deletes the memory allocation with the C library function free if it points to anything valid.

    chastity_checker is the function I am most proud of. It draws a checkboard entirely with some index variables and loops to fill any entire image with a checkerboard. It allows for theoretically using any colors, but in this example, we are outputting to a format that only supports black and white.

    BBM_SavePBM does a lot of things. It creates and opens a file, writes the initial header of the format. calls another function I wrote, BBM_SavePBM_Pixels, to actually write the pixels, then closes the file.

    Understanding the nature of everything that is in the functions requires knowing on a deep level how to use the bitwise AND, OR, XOR, and left and right bit shifts. Without a working knowledge of the Binary Numeral System, you will not at first understand what is happening here.

    However, if you have some understanding of the C library, you will understand the usage of the functions relating to file creation and output.

    Converting the Image File

    The PBM file created will not be readable by most programs. You can instead use some other tools to convert it. You can use the graphical image editor GIMP to load and export it to a different format.

    However, what I do is use ImageMagick from the command line.

    magick image.pbm image.png

    The end result is that the image looks like this:

    pbm-checker.png

    You can also achieve the same result with the tool FFmpeg.

    ffmpeg -i image.pbm image.png

    Graphics Fundamentals

    By now you might be thinking: “Creating image files to display in other programs is great, but can’t I write a program to display a picture without writing it to a write for a different program to view?”

    And it is a very good question. The short answer is yes, but the problem lies with the fact that the C library installed along with the C compiler does not contain anything related to graphics. At the time it was written, computer graphics as we know them today didn’t exist.

    You might wonder how video games are created using the C and C++ languages all the time. There are two main ways to create graphics directly in a windowed desktop application. There is the hard way and the easy way.

    The Hard Way

    The hard way, and for many years the only way, to create a window and draw things onto it is to use the native API for your operating system. I have never learned how to do this because I am only interested in writing software that works on Linux and Windows equally well.

    But what is Linux? Linux is an operating system like Windows or MacOS is. However, it is free and open source! Technically Linux is a kernel and the operating system consists of various component, which includes the X Window System and different Desktop Environments.

    But if you don’t know what Linux is, I am not the best qualified to explain it. However, I have been using Linux for 20 years and I know a thing or two about getting almost any of my C programs compiled on Linux. I can also play Chess on lichess.org just as well on Linux as I can Windows.

    The specific version of Linux I use is called Debian. I would suggest you give it a try if you have an old computer that has Windows but is running too slow.

    https://www.debian.org/

    I know that is a lot of information to take in right now but I mention it because programming on Linux is easier than it is on Windows. That is because the built in package manager lets you install SDL directly without even opening up your web browser and visiting the website! For example, on a Debian or Ubuntu based system, this command will install everything you need to get started with my example SDL program in the next section.

    sudo apt-get install libsdl2-dev

    The Easy Way

    The easy way to make graphical applications and video games is to use SDL. SDL stands for Simple Directmedia Layer. It is a library of functions written by people much smarter than me which act as a layer between what the programmer wants to do and communicating with the operating system to achieve the result.

    I will provide you a short sample of a program written with SDL just so you get an idea how it looks. In a later chapter, I will have to walk you through some things that you need to know to install and compile programs written using the library.

    SDL Square Target

    #include <stdio.h>
    #include <SDL.h>
    int width=720,height=720;
    int loop=1;
    SDL_Window *window;
    SDL_Surface *surface;
    SDL_Event e;
    int main(int argc, char **argv)
    {
     int x,y;
     int colors[]={0x000000,0xFFFFFF},index=0;
     int rect_width=30,rect_height=30;
     SDL_Rect rect;
    
     if(SDL_Init(SDL_INIT_VIDEO))
     {
      printf( "SDL could not initialize! SDL_Error: %s\n",SDL_GetError());return -1;
     }
     window=SDL_CreateWindow("SDL Program",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,width,height,SDL_WINDOW_SHOWN );
     if(window==NULL){printf( "Window could not be created! SDL_Error: %s\n", SDL_GetError() );return -1;}
     surface = SDL_GetWindowSurface( window ); /*get surface for this window*/
    
     /*drawing section begin*/
    
     rect.w=width;
     rect.h=height;
    
     x=0;
     y=0;
    
     while(rect.w>0)
     {
      rect.x=x;
      rect.y=y;
      SDL_FillRect(surface,&rect,colors[index]);
      x+=rect_width;
      y+=rect_height;
      rect.w-=rect_width*2;
      rect.h-=rect_height*2;
      index^=1;
     }
    
     /*drawing section end*/
    
     SDL_UpdateWindowSurface(window);
    
     printf("SDL Program Compiled Correctly\n");
    
     while(loop)
     {
      while(SDL_PollEvent(&e))
      {
       if(e.type == SDL_QUIT){loop=0;}
       if(e.type == SDL_KEYUP)
       {
        if(e.key.keysym.sym==SDLK_ESCAPE){loop=0;}
       }
      }
     }
     SDL_DestroyWindow(window);
     SDL_Quit();
     return 0;
    }
    

    On my Debian Linux system, I use this command to compile and run this program.

    gcc -Wall -ansi -pedantic main.c -o main `sdl2-config --cflags --libs` -lm && ./main
    

    When you successfully compile and run the program, it will look something like this!

    sdl-square-target.png

    If you look at the source code, you will see that it has a lot of code that must be done before we even get to the drawing section that I marked with comments at the beginning and end.

    When I first started using SDL version 2, it did not have support for drawing anything other than rectangles. There is an advanced way to do with by alternative means but that is beyond the scope of this book.

    However, rectangles are all you need to make an entire Tetris game! I wrote all the code for my own game and published it on Steam.

    https://store.steampowered.com/app/1986120/Chaste_Tris/

    I will admit the code was a complete wreck and the game is not as advanced as the officially licensed Tetris games. However, as a proof of concept, and for the sake of making a video game so I can cross it off my bucket list, I did it.

    Before you leave this chapter, I want you to know more than one thing, the square is a special case of a rectangle where the width and height are the same. My obsession with the games Tetris and Chess most certainly is because I am obsessed with the shape of the square! Therefore, all of my graphics programming experience came about from my desire to draw perfect checkerboards on the computer. I have succeeded!

    Chapter 3: Back to Binary Basics

    In chapter 1 I showed you my favorite integer sequences and how easy it is to generate them with a bit of math and printf statements in the C Programming Language. However, a lot of the code in my powers of two program and also the Portable Bit Map Checkerboard program do not make sense unless you understand that nature of the binary numeral system. To help illustrate it, I have created a small program which displays the numbers 0 to 15.

    Binary to Decimal Counting 4 Bits

    #include <stdio.h>
    #include "binlib.h"
    int main()
    {
     int a=0;
     while(a<16)
     {
      printf("%s %02d\n",int_to_binary_string(a,4),a);
      a++;
     }
     return 0;
    }
    

    The included file “binlib.h” contains this function:

    #define ulength 100  /*the size of the array to be defined next*/
    char u[ulength]; /*universal array for my integer to binary string function*/
    
    char* int_to_binary_string(unsigned int i,int width)
    {
     char *s=u+ulength;
     *s=0;
     do
     {
      s--;
      *s=i&1;
      i>>=1;
      *s+='0';
      width--;
     }
     while(i!=0 || width>0);
     return s;
    }
    

    When run, the program displays this:

    0000 00
    0001 01
    0010 02
    0011 03
    0100 04
    0101 05
    0110 06
    0111 07
    1000 08
    1001 09
    1010 10
    1011 11
    1100 12
    1101 13
    1110 14
    1111 15
    

    The numbers on the left are the binary version and the numbers on the right are the decimal numbers you have been taught all your life by human society. The truth is, there are as many different bases to use for a number system as there are numbers themselves.

    By looking at the chart above, you can probably figure out how binary works already, but in case you are stuck, here is another small sample program and its output to drive the point even clearer.

    Left Shift Example

    #include <stdio.h>
    #include "binlib.h"
    int main()
    {
     int a=1;
     while(a!=0)
     {
      printf("%s %10u\n",int_to_binary_string(a,32),a);
      a<<=1;
     }
     return 0;
    }
    

    The output of this program is:

    00000000000000000000000000000001          1
    00000000000000000000000000000010          2
    00000000000000000000000000000100          4
    00000000000000000000000000001000          8
    00000000000000000000000000010000         16
    00000000000000000000000000100000         32
    00000000000000000000000001000000         64
    00000000000000000000000010000000        128
    00000000000000000000000100000000        256
    00000000000000000000001000000000        512
    00000000000000000000010000000000       1024
    00000000000000000000100000000000       2048
    00000000000000000001000000000000       4096
    00000000000000000010000000000000       8192
    00000000000000000100000000000000      16384
    00000000000000001000000000000000      32768
    00000000000000010000000000000000      65536
    00000000000000100000000000000000     131072
    00000000000001000000000000000000     262144
    00000000000010000000000000000000     524288
    00000000000100000000000000000000    1048576
    00000000001000000000000000000000    2097152
    00000000010000000000000000000000    4194304
    00000000100000000000000000000000    8388608
    00000001000000000000000000000000   16777216
    00000010000000000000000000000000   33554432
    00000100000000000000000000000000   67108864
    00001000000000000000000000000000  134217728
    00010000000000000000000000000000  268435456
    00100000000000000000000000000000  536870912
    01000000000000000000000000000000 1073741824
    10000000000000000000000000000000 2147483648
    

    The binary system works exactly like you would expect given the chart above. Each bit is either 0 (off) or it is a 1 which represents that a specific power of two is turned “on”.

    For example, add this function to “binlib.h”

    int binary_string_to_int(char *s)
    {
     int i=0;
     char c;
     while( *s == ' ' || *s == '\n' || *s == '\t' ){s++;} /*skip whitespace at beginning*/
     while(*s!=0)
     {
      c=*s;
      if( c == '0' || c == '1' ){c-='0';}
      else if( c == ' ' || c == '\n' || c == '\t' ){return i;}
      else{printf("Error: %c is not a valid character for base 2.\n",c);return i;}
      i<<=1;
      i+=c;
      s++;
     }
     return i;
    }
    

    And then the following program will display my age!

    Binary Age Example

    #include <stdio.h>
    #include "binlib.h"
    int main()
    {
     printf("My name is Chastity and I am %d years old!\n",binary_string_to_int("100110"));
     return 0;
    }
    

    The result is:

    My name is Chastity and I am 38 years old!

    This works because we are literally sending a binary string which represents 32+0+0+4+2+0.

    Perhaps you are starting to understand how it works by now. If not, don’t worry because technically you don’t need to know binary to be a computer programmer. but some advanced techniques in graphics programming are not possible without a working knowledge of how many bits a data type is and how to modify individual bits.

    Why is Binary Used?

    But perhaps the larger question is why Binary is used in computers. Basically the idea is that states of on/off or low/hi voltage are how electricity is measured. Once you understand that everything in the world can be represented by a number and that Binary is one such system to represent numbers, you can see why it is so popular in computer hardware and software.

    Anyhow, welcome to the art of computer programming, where black and white thinking really does help you! Or perhaps I should say, welcome to autism!

    The Bitwise Operations

    There are 5 bitwise operations which operate on the bits of data in a computer. For the purpose of demonstration, it doesn’t matter which number the bits represent at the moment. This is because the bits don’t have to represent numbers at all but can represent anything described in two states. Bits are commonly used to represent statements that are true or false. For the purposes of this section, the words AND, OR, XOR are in capital letters because their meaning is only loosely related to the Englist words they get their name from.

    Bitwise AND Operation

    0 AND 0 == 0
    0 AND 1 == 0	
    1 AND 0 == 0
    1 AND 1 == 1
    

    Think of the bitwise AND operation as multiplication of single bits. 1 times 1 is always 1 but 0 times anything is always 0. That’s how I personally think of it. I guess you could say that something is true only if two conditions are true. For example, if I go to Walmart AND do my job then it is true that I get paid.

    Bitwise OR Operation

    0 OR 0 == 0
    0 OR 1 == 1	
    1 OR 0 == 1
    1 OR 1 == 1
    

    The bitwise OR operation can be thought of as something that is true if one or two conditions are true. For example, it is true that playing in the street will result in you dying because you got run over by a car. It is also true that if you live long enough, something else will kill you. Therefore, the bit of your impending death is always 1.

    Bitwise XOR Operation

    0 XOR 0 == 0
    0 XOR 1 == 1	
    1 XOR 0 == 1
    1 XOR 1 == 0
    

    The bitwise XOR operation is different because it isn’t really used much for evaluating true or false. Instead, it is commonly used to invert a bit. For example, if you go back to the source of my graphics programs in Chapter 2, you will see that most of those programs contain the statement:

    index^=1;

    If you look at my XOR chart above, you will see that using XOR of any bit with a 1 causes the result to be the opposite of the original bit. In the context of those programs, the index variable is meant to be 0 to represent black and 1 to represent white. The XOR operation is the quickest way to achieve this bit inversion. In fact, in all my years of programming, that’s pretty much the only thing I have used it for!

    Bitwise Left and Right Shift Operations

    Consider the case of the following 8 bit value:

    00001000

    This would of course represent the number 8 because a 1 is in the 8’s place value. We can left shift or right shift.

    00001000 ==  8 : is the original byte
    
    00010000 == 16 : after left shift
    00000100 ==  4 : after right shift
    

    Left and right shift operations allow us to multiply or divide a number by 2 by taking advantage of the base 2 system. These shifts are essential in graphics programming because sometimes to need to extract the red, green, or blue values separately out of their 24 bit representation. For example, consider this code:

       pixel=p[x+y*width];
       r=(pixel&0xFF0000)>>16;
       g=(pixel&0x00FF00)>>8;
       b=(pixel&0x0000FF);
    

    The first statement gets the pixel out of an array of data which is indexed by x and y geometric coordinates. This will be a 24 bit value, or in some cases 32 bit with the highest 8 bits representing the alpha or transparency level.

    variables r,g,b represent red, green, and blue. With clever use of bitwise AND operations and right shifting by the correct number of bits, it is possible to extract just that color component to be modified. Without the ability to do this, my graphics animations and my Tetris game would never have been possible. The colors had to be exactly sent to the drawing functions. This is true not just for SDL but using any graphical system involving colors.

    Learning More

    I know I covered a lot in this chapter but I encourage you to learn about the binary numeral system and its close cousin the hexadecimal system. If you do an online search, you will find courses, tutorials, and videos by millions of people who can probably explain these same concepts in a way that you understand better if you are still confused after reading this chapter!