Write Great Code - Randall Hyde Date: 2023-11-26 | The lowercase character symbols use the ASCII codes $61 through | $7A. If you convert the codes for the upper- and lowercase characters | to binary, you’ll notice that the uppercase symbols differ from | their lowercase equivalents in exactly one bit position Chapter: 5.1 Character Data Date: 2023-11-26 | These two codes differ only in bit 5. Uppercase alphabetic characters | always contain a 0 in bit 5; lowercase alphabetic characters always | contain a 1 in bit 5. To quickly convert an alphabetic character | between upper- and lowercase, simply invert bit 5 Chapter: 5.1 Character Data Date: 2023-11-26 | Bits 5 and 6 determine the character’s group (see Table | 5-1). Therefore, you can convert any upper- or lowercase (or special) | character to its corresponding control character by setting bits 5 | and 6 to 0 (for example, A becomes CTRL-A when you set bits 5 and | 6 to 0; that is, 0x41 becomes 0x01 Chapter: 5.1 Character Data Date: 2023-11-28 | Bits 5 and 6 aren’t the only bits that encode useful | information. Consider, for a moment, the ASCII codes of the numeric | digit characters in Table 5-2. The decimal representations of these | ASCII codes are not very enlightening. However, the hexadecimal | representation reveals something very important—the LO nibble is | the binary equivalent of the represented number. By stripping away | (setting to 0) the HO nibble of the ASCII code, you obtain the | binary representation of that digit. Conversely, you can convert | a binary value in the range 0 through 9 to its ASCII character | representation by simply setting the HO nibble to %0011, or the | decimal value 3. You can use the logical AND operation to force | the HO bits to 0; likewise, you can use the logical OR operation | to force the HO bits to %0011. Chapter: 5.1 Character Data Date: 2023-04-06 | Processor size is determined by whichever value is smaller: the | number of data lines on the processor or the size of the largest | general-purpose integer register. Chapter: 6.1 The Basic System Components Date: 2023-04-06 | The term byte-addressable memory array means that the CPU can | address memory in chunks as small as a single byte. Chapter: 6.2 Physical Organization of Memory Date: 2023-11-14 | Memory access is an operation that is synchronized with the system | clock; that is, memory access occurs no more than once every clock | cycle. On some older processors, it takes several clock cycles to | access a memory location. The memory access time is the number of | clock cycles between a memory request (read or write) and when the | memory operation completes. Chapter: 6.4 The System Clock Date: 2023-11-14 | The CPU doesn’t wait for memory. The access time is specified | by the bus clock frequency. If the memory subsystem doesn’t work | fast enough to keep up with the CPU’s expected access time, the | CPU will read garbage data on a memory read operation and will not | properly store the data on a memory write. This will surely cause | the system to fail. Chapter: 6.4 The System Clock Date: 2023-11-16 | A typical program tends to access the same memory locations | repeatedly (known as temporal locality of reference), and to access | adjacent memory locations (spatial locality of reference Chapter: 6.4 The System Clock Date: 2023-11-16 | Cache memory is a small amount of very fast memory that sits between | the CPU and main memory. Chapter: 6.4 The System Clock Date: 2023-11-16 | A cache hit occurs whenever the CPU accesses memory and finds the | data in the cache. In such a case, the CPU can usually access data | with zero wait states. A cache miss occurs if the data cannot be | found in the cache. Chapter: 6.4 The System Clock Date: 2023-11-16 | when a cache miss occurs, most caching systems will read several | consecutive bytes of main memory (which engineers call a cache | line). For example, 80x86 CPUs read between 16 and 64 bytes upon | a cache miss. Most memory chips available today have special modes | that let you quickly access several consecutive memory locations on | the chip. The cache exploits this capability to reduce the average | number of wait states needed to access sequential memory locations Chapter: 6.4 The System Clock Date: 2023-11-16 | Whereas the 80x86 frequently accesses memory, RISC processors | rarely do. Therefore, that RISC processor can probably execute | the first four instructions, which do not access memory at all, | while the single 80x86 instruction, which does access memory, | is spinning on some wait states Chapter: 6.5 CPU Memory Access Date: 2023-11-23 | Choosing an appropriate addressing mode often enables an application | to compute the same result with fewer instructions and with fewer | memory accesses, thus improving performance. Therefore, if you want | to write fast and compact code, it’s important to understand how | an application can use the different addressing modes a CPU provides. Chapter: 6.5 CPU Memory Access Date: 2023-11-30 | Most CPUs use byte addresses for memory objects. Therefore, when a | program allocates multiple copies of some n-byte object in memory, | the objects won’t begin at consecutive memory addresses; instead, | they’ll appear in memory at addresses that are n bytes apart. Chapter: 7.1 Pointer Types Date: 2023-11-30 | Any attempt to access memory on some other boundary will raise an | exception and potentially halt the application. If an HLL supports | pointer arithmetic, it must account for this fact and provide a | generic pointer arithmetic scheme that’s portable across many | different CPU architectures. Chapter: 7.1 Pointer Types Date: 2023-11-30 | The most common solution that HLLs use when adding an integer offset | to a pointer is to multiply that offset by the size of the object | that the pointer references. That is, if you’ve got a pointer p | to a 16-byte object in memory, then p + 1 points 16 bytes beyond | the address where p points. Likewise, p + 2 points 32 bytes beyond | that address. As long as the size of the data object is a multiple | of the required alignment size (which the compiler can enforce by | adding padding bytes, if necessary), this scheme avoids problems | on those architectures that require aligned data access. Chapter: 7.1 Pointer Types Date: 2023-11-30 | you can also add a pointer to an integer and the result is still a | pointer (both p + i and i + p are legal). This is because addition | is commutative—the order of the operands does not affect the result Chapter: 7.1 Pointer Types Date: 2023-11-30 | subtraction is not commutative, and subtracting a pointer from an | integer is not a legal operation (p - i is legal, but i - p is not). Chapter: 7.1 Pointer Types Date: 2023-12-02 | For pointer subtraction in C/C++, the base types of the two pointers | must be identical (that is, the two pointers must contain the | addresses of two objects whose types are identical). This restriction | exists because pointer subtraction in C/C++ produces the number of | objects, not the number of bytes, between the two pointers. Chapter: 7.1 Pointer Types Date: 2023-12-02 | Comparing two pointers will tell you whether they reference the | same object in memory. Chapter: 7.1 Pointer Types Date: 2023-12-02 | Some languages (such as assembly and C/C++) will also let you compare | two pointers to see if one pointer is less than or greater than | the other. Such a comparison only makes sense, however, if both | pointers have the same base type and contain the address of some | object within the same data structure (such as an array, string, | or record). If you find that one pointer is less than the other, | this tells you that it references an object within the data structure | that appears before the object referenced by the second pointer. The | converse is true for the greater-than comparison. Chapter: 7.1 Pointer Types Date: 2023-12-02 | The base address of an array is the address of its first element | and occupies the lowest memory location. The second array element | directly follows the first in memory, the third element follows | the second, and so on. There is no requirement that the indices | start at 0; they can start with any number as long as they’re | contiguous. However, we’ll begin arrays at index 0 unless there’s | a good reason to do otherwise. Chapter: 7.2 Arrays Date: 2023-12-02 | The number of bytes of storage an array consumes is the product of | the number of elements multiplied by the number of bytes per element | in the array. Many languages also add a few bytes of padding at the | end of the array so that the total length of the array is an even | multiple of a nice value like 4 or 8 (on a 32- or 64-bit machine, | a compiler may append bytes to the array in order to extend its | length to some multiple of the machine’s word size Chapter: 7.2 Arrays Date: 2023-12-02 | Many optimizing compilers attempt to start an array at a memory | address that is an even multiple of some common size like 2, 4, or | 8 bytes. Effectively, this adds padding bytes before the beginning | of the array or, if you prefer to think of it this way, after the | previous object in memory Chapter: 7.2 Arrays Date: 2023-12-02 | If the size of each array element is less than the minimum size | memory object the CPU supports, the compiler implementer has two | options: Allocate the smallest accessible memory object for each | element of the array. Pack multiple array elements into a single | memory cell. Chapter: 7.2 Arrays Date: 2023-12-02 | If you’re working on a byte-addressable machine (like the 80x86), | you probably don’t have to worry about this issue. However, | if you’re using an HLL and your code might wind up running on | a different machine in the future, you should choose an array | organization that is efficient on all machines Chapter: 7.2 Arrays Date: 2023-12-02 | If you allocate all the storage for an array in contiguous memory | locations, and the first index of the array is 0, then accessing an | element of a one-dimensional array is simple. You can compute the | address of any given element of an array using the following formula: | Element_Address = Base_Address + index * Element_Size Element_Size | is the number of bytes that each array element occupies. Thus, | if each array element is of type byte, the Element_Size field is | 1 and the computation is very simple. If each element is a word | (or another 2-byte type), then Element_Size is 2, and so on. Chapter: 7.2 Arrays Date: 2023-12-02 | The actual mapping is not important as long as it adheres to | two rules: No two entries in the array can occupy the same memory | location(s). Each element in the array must always map to the same | memory location. Chapter: 7.2 Arrays Date: 2023-12-02 | Row-major ordering assigns array elements to successive memory | locations by moving across a row and then down the columns. Chapter: 7.2 Arrays Date: 2023-12-02 | If you’ve got an n-dimensional array declared in C/C++ as follows: | dataType A[bn-1][bn-2]...[b0]; and you wish to access the following | element of this array: A[an-1][an-2]...[a1][a0] then you can compute | the address of a particular array element using the following | algorithm: Address := an-1 for i := n-2 downto 0 do     Address | := Address * bi + ai Address := Base_Address + Address * Element_Size Chapter: 7.2 Arrays Date: 2023-12-02 | Because C++ structures are actually a specialized form of the | class declaration, they behave differently from C structures | and may include extra data in memory that is not present in the C | variant. (This is why the memory storage for structures in C++ may be | different; see “Memory Storage of Records” on page 184). There | are also differences in namespaces and other minor distinctions | between C and C++ structures. As it turns out, though, you can | tell C++ to compile a true C struct definition using the extern | "C" block as follows: Chapter: 7.3 Records/Structures Date: 2023-12-02 | Like arrays in Swift, tuples are an opaque type, without a guaranteed | definition for how Swift will store them in memory. Chapter: 7.3 Records/Structures Date: 2023-12-02 | For performance reasons, most compilers actually align the fields of | a record on appropriate memory boundaries. The exact details vary by | language, compiler implementation, and CPU, but a typical compiler | places fields at an offset within the record’s storage area that is | “natural” for that particular field’s data type. On the 80x86, | for example, compilers that follow the Intel ABI (application binary | interface) allocate 1-byte objects at any offset within the record, | words only at even offsets, and double-word or larger objects on | double-word boundaries. Chapter: 7.3 Records/Structures Date: 2023-12-12 | the big difference between a union and a record is the fact that | records allocate storage for each field at different offsets, whereas | unions overlay all of the fields at the same offset in memory. Chapter: 7.4 Discriminant Unions Date: 2023-12-13 | in some section of your program you might need to constantly use | type coercion to refer to a particular object. To avoid this, you | could use a union variable with each field representing one of the | different types you want to use for the object. Chapter: 7.4 Discriminant Unions Date: 2023-12-13 | The HO and LO bytes of a multibyte object appear at different | addresses on big-endian versus little-endian machines. Chapter: 7.4 Discriminant Unions Date: 2023-12-13 | Obtaining the method address from the VMT is a lot of work. Why | would the compiled code do this rather than calling the method | directly? The reason is because of a pair of magical features that | classes and objects support: inheritance and polymorphism. Chapter: 7.5 Classes Date: 2023-12-24 | an important attribute of a class that inherits fields from a base | class is that you can use a pointer to the base class to access its | fields, even if the pointer contains the address of some other class Chapter: 7.5 Classes Date: 2023-12-24 | For inheritance to work properly, the i, j, and r fields must | appear at the same offsets in all child classes as they do in | tBaseClass. This way, an instruction of the form mov((type tBaseClass | [ebx]).i, eax); will correctly access the i field even if EBX points | at an object of type tChildClassA or tChildClassB. Chapter: 7.5 Classes Date: 2024-01-17 | Boolean algebra is a deductive mathematical system. Chapter: 8.1 Boolean Algebra Date: 2024-01-17 | Logical complement, logical negation, and NOT are all names for | the same unary operator. Chapter: 8.1 Boolean Algebra Date: 2024-01-17 | Commutativity Chapter: 8.1 Boolean Algebra Date: 2024-01-18 | Th7   (A + B)' = A' • B Chapter: 8.1 Boolean Algebra Date: 2023-02-24 | instruction’s binary numeric code (also known as an operation | code or opcode) Chapter: 9.1 Basic CPU Design Date: 2023-02-24 | EIP (extended instruction pointer Chapter: 9.3 Executing Instructions, Step by Step Date: 2023-02-24 | The flags register, also known as the condition-codes register or | program-status word, is an array of Boolean variables in the CPU | that tracks whether the previous instruction produced an overflow, | a zero result, a negative result, or other such condition. Chapter: 9.3 Executing Instructions, Step by Step