Cover image for Write Great Code, Volume 2

Book description

The second volume in the Write Great Code series supplies the critical information that today's computer science students don't often get from college and university courses: How to carefully choose their high-level language statements to produce efficient code. Write Great Code, Volume 2: Thinking Low-Level, Writing High-Level , teaches software engineers how compilers translate high-level language statements and data structures into machine code. Armed with this knowledge, a software engineer can make an informed choice concerning the use of those high-level structures to help the compiler produce far better machine code--all without having to give up the productivity and portability benefits of using a high-level language.

Table of Contents

  1. Write Great Code, Volume 2
  2. Acknowledgments
  3. Introduction
  4. 1. Thinking Low-Level, Writing High-Level
    1. 1.1 Misconceptions About Compiler Quality
    2. 1.2 Why Learning Assembly Language Is Still a Good Idea
    3. 1.3 Why Learning Assembly Language Isn’t Absolutely Necessary
    4. 1.4 Thinking Low-Level
      1. 1.4.1 Compilers Are Only as Good as the Source Code You Feed Them
      2. 1.4.2 Helping the Compiler Produce Better Machine Code
      3. 1.4.3 How to Think in Assembly While Writing HLL Code
    5. 1.5 Writing High-Level
    6. 1.6 Assumptions
    7. 1.7 Language-Neutral Approach
    8. 1.8 Characteristics of Great Code
    9. 1.9 The Environment for This Text
    10. 1.10 For More Information
  5. 2. Shouldn’t You Learn Assembly Language?
    1. 2.1 Roadblocks to Learning Assembly Language
    2. 2.2 Write Great Code, Volume 2, to the Rescue
    3. 2.3 High-Level Assemblers to the Rescue
    4. 2.4 The High-Level Assembler (HLA)
    5. 2.5 Thinking High-Level, Writing Low-Level
    6. 2.6 The Assembly Programming Paradigm (Thinking Low-Level)
    7. 2.7 The Art of Assembly Language and Other Resources
  6. 3. 80x86 Assembly for the HLL Programmer
    1. 3.1 Learning One Assembly Language Is Good, Learning More Is Better
    2. 3.2 80x86 Assembly Syntaxes
    3. 3.3 Basic 80x86 Architecture
      1. 3.3.1 Registers
      2. 3.3.2 80x86 General-Purpose Registers
      3. 3.3.3 The 80x86 EFLAGS Register
    4. 3.4 Literal Constants
      1. 3.4.1 Binary Literal Constants
        1. 3.4.1.1 Binary Literal Constants in HLA
        2. 3.4.1.2 Binary Literal Constants in Gas
        3. 3.4.1.3 Binary Literal Constants in MASM and TASM
      2. 3.4.2 Decimal Literal Constants
        1. 3.4.2.1 Decimal Literal Constants in HLA
        2. 3.4.2.2 Decimal Literal Constants in Gas, MASM, and TASM
      3. 3.4.3 Hexadecimal Literal Constants
        1. 3.4.3.1 Hexadecimal Literal Constants in HLA
        2. 3.4.3.2 Hexadecimal Literal Constants in Gas
        3. 3.4.3.3 Hexadecimal Literal Constants in MASM and TASM
      4. 3.4.4 Character and String Literal Constants
        1. 3.4.4.1 Character and String Literal Constants in HLA
        2. 3.4.4.2 Character and String Literal Constants in Gas
        3. 3.4.4.3 Character/String Literal Constants in MASM and TASM
      5. 3.4.5 Floating-Point Literal Constants
    5. 3.5 Manifest (Symbolic) Constants in Assembly Language
      1. 3.5.1 Manifest Constants in HLA
      2. 3.5.2 Manifest Constants in Gas
      3. 3.5.3 Manifest Constants in MASM and TASM
    6. 3.6 80x86 Addressing Modes
      1. 3.6.1 80x86 Register Addressing Modes
        1. 3.6.1.1 Register Access in HLA
        2. 3.6.1.2 Register Access in Gas
        3. 3.6.1.3 Register Access in MASM and TASM
      2. 3.6.2 Immediate Addressing Mode
      3. 3.6.3 Displacement-Only Memory Addressing Mode
      4. 3.6.4 Register Indirect Addressing Mode
        1. 3.6.4.1 Register Indirect Modes in HLA
        2. 3.6.4.2 Register Indirect Modes in MASM and TASM
        3. 3.6.4.3 Register Indirect Modes in Gas
      5. 3.6.5 Indexed Addressing Mode
        1. 3.6.5.1 Indexed Addressing Mode in HLA
        2. 3.6.5.2 Indexed Addressing Mode in MASM and TASM
        3. 3.6.5.3 Indexed Addressing Mode in Gas
      6. 3.6.6 Scaled-Indexed Addressing Modes
        1. 3.6.6.1 Scaled-Indexed Addressing in HLA
        2. 3.6.6.2 Scaled-Indexed Addressing in MASM and TASM
        3. 3.6.6.3 Scaled-Indexed Addressing in Gas
    7. 3.7 Declaring Data in Assembly Language
      1. 3.7.1 Data Declarations in HLA
      2. 3.7.2 Data Declarations in MASM and TASM
      3. 3.7.3 Data Declarations in Gas
        1. 3.7.3.1 Accessing Byte Variables in Assembly Language
    8. 3.8 Specifying Operand Sizes in Assembly Language
      1. 3.8.1 Type Coercion in HLA
      2. 3.8.2 Type Coercion in MASM and TASM
      3. 3.8.3 Type Coercion in Gas
    9. 3.9 The Minimal 80x86 Instruction Set
    10. 3.10 For More Information
  7. 4. PowerPC Assembly for the HLL Programmer
    1. 4.1 Learning One Assembly Language Is Good; More Is Better
    2. 4.2 Assembly Syntaxes
    3. 4.3 Basic PowerPC Architecture
      1. 4.3.1 General-Purpose Integer Registers
      2. 4.3.2 General-Purpose Floating-Point Registers
      3. 4.3.3 User-Mode-Accessible Special-Purpose Registers
        1. 4.3.3.1 Condition-Code Registers
        2. 4.3.3.2 Floating-Point Status and Control Register
        3. 4.3.3.3 XER Register
        4. 4.3.3.4 The LINK Register
        5. 4.3.3.5 The COUNT Register
        6. 4.3.3.6 The Time Base Registers (TBL and TBU)
    4. 4.4 Literal Constants
      1. 4.4.1 Binary Literal Constants
      2. 4.4.2 Decimal Literal Constants
      3. 4.4.3 Hexadecimal Literal Constants
      4. 4.4.4 Character and String Literal Constants
      5. 4.4.5 Floating-Point Literal Constants
    5. 4.5 Manifest (Symbolic) Constants in Assembly Language
    6. 4.6 PowerPC Addressing Modes
      1. 4.6.1 PowerPC Register Access
      2. 4.6.2 The Immediate Addressing Mode
      3. 4.6.3 PowerPC Memory Addressing Modes
        1. 4.6.3.1 Register Plus Displacement Addressing Mode
        2. 4.6.3.2 Register Plus Register (Indexed) Addressing Mode
    7. 4.7 Declaring Data in Assembly Language
    8. 4.8 Specifying Operand Sizes in Assembly Language
    9. 4.9 The Minimal Instruction Set
    10. 4.10 For More Information
  8. 5. Compiler Operation and Code Generation
    1. 5.1 File Types That Programming Languages Use
    2. 5.2 Programming Language Source Files
      1. 5.2.1 Tokenized Source Files
      2. 5.2.2 Specialized Source File Formats
    3. 5.3 Types of Computer Language Processors
      1. 5.3.1 Pure Interpreters
      2. 5.3.2 Interpreters
      3. 5.3.3 Compilers
      4. 5.3.4 Incremental Compilers
    4. 5.4 The Translation Process
      1. 5.4.1 Lexical Analysis and Tokens
      2. 5.4.2 Parsing (Syntax Analysis)
      3. 5.4.3 Intermediate Code Generation
      4. 5.4.4 Optimization
        1. 5.4.4.1 The Problem with Optimization
        2. 5.4.4.2 Optimization’s Effect on Compile Time
        3. 5.4.4.3 Basic Blocks, Reducible Code, and Optimization
        4. 5.4.4.4 Common Compiler Optimizations
        5. 5.4.4.5 Controlling Compiler Optimization
      5. 5.4.5 Comparing Different Compilers’ Optimizations
      6. 5.4.6 Native Code Generation
    5. 5.5 Compiler Output
      1. 5.5.1 Emitting HLL Code as Compiler Output
      2. 5.5.2 Emitting Assembly Language as Compiler Output
      3. 5.5.3 Emitting Object Files as Compiler Output
      4. 5.5.4 Emitting Executable Files as Compiler Output
    6. 5.6 Object File Formats
      1. 5.6.1 The COFF File Header
      2. 5.6.2 The COFF Optional Header
      3. 5.6.3 COFF Section Headers
      4. 5.6.4 COFF Sections
      5. 5.6.5 The Relocation Section
      6. 5.6.6 Debugging and Symbolic Information
      7. 5.6.7 Learning More About Object File Formats
    7. 5.7 Executable File Formats
      1. 5.7.1 Pages, Segments, and File Size
      2. 5.7.2 Internal Fragmentation
      3. 5.7.3 So Why Optimize for Space?
    8. 5.8 Data and Code Alignment in an Object File
      1. 5.8.1 Choosing a Section Alignment Size
      2. 5.8.2 Combining Sections
      3. 5.8.3 Controlling the Section Alignment
      4. 5.8.4 Section Alignment and Library Modules
    9. 5.9 Linkers and Their Effect on Code
    10. 5.10 For More Information
  9. 6. Tools for Analyzing Compiler Output
    1. 6.1 Background
    2. 6.2 Telling a Compiler to Produce Assembly Output
      1. 6.2.1 Assembly Output from GNU and Borland Compilers
      2. 6.2.2 Assembly Output from Visual C++
      3. 6.2.3 Example Assembly Language Output
        1. 6.2.3.1 Visual C++ Assembly Language Output
        2. 6.2.3.2 Borland C++ Assembly Language Output
        3. 6.2.3.3 Borland C++/Intel Backend Assembly Output
        4. 6.2.3.4 GCC Assembly Language Output (PowerPC)
        5. 6.2.3.5 GCC Assembly Language Output (80x86)
      4. 6.2.4 Analyzing Assembly Output from a Compiler
    3. 6.3 Using Object-Code Utilities to Analyze Compiler Output
      1. 6.3.1 The Microsoft dumpbin.exe Utility
        1. 6.3.1.1 The dumpbin.exe /all Command-Line Option
        2. 6.3.1.2 The dumpbin.exe /disasm Command-Line Option
        3. 6.3.1.3 The dumpbin.exe /headers Command-Line Option
        4. 6.3.1.4 The dumpbin.exe /imports Command-Line Option
        5. 6.3.1.5 The dumpbin.exe /relocations Command-Line Option
        6. 6.3.1.6 Other dumpbin.exe Command-Line Options
      2. 6.3.2 The FSF/GNU objdump.exe Utility
    4. 6.4 Using a Disassembler to Analyze Compiler Output
    5. 6.5 Using a Debugger to Analyze Compiler Output
      1. 6.5.1 Using an IDE’s Debugger
      2. 6.5.2 Using a Stand-Alone Debugger
    6. 6.6 Comparing Output from Two Compilations
      1. 6.6.1 Before-and-After Comparisons with diff
      2. 6.6.2 Manual Comparison
    7. 6.7 For More Information
  10. 7. Constants and High-Level Languages
    1. 7.1 Literal Constants and Program Efficiency
    2. 7.2 Literal Constants Versus Manifest Constants
    3. 7.3 Constant Expressions
    4. 7.4 Manifest Constants Versus Read-Only Memory Objects
    5. 7.5 Enumerated Types
    6. 7.6 Boolean Constants
    7. 7.7 Floating-Point Constants
    8. 7.8 String Constants
    9. 7.9 Composite Data Type Constants
    10. 7.10 For More Information
  11. 8. Variables in a High-Level Language
    1. 8.1 Runtime Memory Organization
      1. 8.1.1 The Code, Constant, and Read-Only Sections
      2. 8.1.2 The Static Variables Section
      3. 8.1.3 The BSS Section
      4. 8.1.4 The Stack Section
      5. 8.1.5 The Heap Section and Dynamic Memory Allocation
    2. 8.2 What Is a Variable?
      1. 8.2.1 Attributes
      2. 8.2.2 Binding
      3. 8.2.3 Static Objects
      4. 8.2.4 Dynamic Objects
      5. 8.2.5 Scope
      6. 8.2.6 Lifetime
      7. 8.2.7 So What Is a Variable?
    3. 8.3 Variable Storage
      1. 8.3.1 Static Binding and Static Variables
        1. 8.3.1.1 Binding at Language-Design Time
        2. 8.3.1.2 Binding at Compile Time
        3. 8.3.1.3 Binding at Link Time
        4. 8.3.1.4 Binding at Load Time
        5. 8.3.1.5 Static Variable Binding
      2. 8.3.2 Pseudo-Static Binding and Automatic Variables
      3. 8.3.3 Dynamic Binding and Dynamic Variables
    4. 8.4 Common Primitive Data Types
      1. 8.4.1 Integer Variables
      2. 8.4.2 Floating-Point/Real Variables
      3. 8.4.3 Character Variables
      4. 8.4.4 Boolean Variables
    5. 8.5 Variable Addresses and High-level Languages
      1. 8.5.1 Storage Allocation for Global and Static Variables
      2. 8.5.2 Using Automatic Variables to Reduce Offset Sizes
      3. 8.5.3 Storage Allocation for Intermediate Variables
      4. 8.5.4 Storage Allocation for Dynamic Variables and Pointers
      5. 8.5.5 Using Records/Structures to Reduce Instruction Offset Sizes
      6. 8.5.6 Register Variables
    6. 8.6 Variable Alignment in Memory
      1. 8.6.1 Records and Alignment
    7. 8.7 For More Information
  12. 9. Array Data Types
    1. 9.1 What Is an Array?
      1. 9.1.1 Array Declarations
        1. 9.1.1.1 Declaring Arrays in C, C++, and Java
        2. 9.1.1.2 Declaring Arrays in HLA
        3. 9.1.1.3 Declaring Arrays in Pascal, Delphi, and Kylix
        4. 9.1.1.4 Declaring Arrays with Noninteger Index Values
      2. 9.1.2 Array Representation in Memory
      3. 9.1.3 Accessing Elements of an Array
      4. 9.1.4 Padding Versus Packing
      5. 9.1.5 Multidimensional Arrays
        1. 9.1.5.1 Declaring Multidimensional Arrays
        2. 9.1.5.2 Mapping Multidimensional Array Elements to Memory
        3. 9.1.5.3 Row-Major Ordering
        4. 9.1.5.4 Column-Major Ordering
        5. 9.1.5.5 Accessing Elements of a Multidimensional Array
        6. 9.1.5.6 Emulating Column-Major or Row-Major Ordering
        7. 9.1.5.7 Improving Array Access Efficiency in Your Applications
      6. 9.1.6 Dynamic Versus Static Arrays
        1. 9.1.6.1 Single-Dimensional Pseudo-Dynamic Arrays
        2. 9.1.6.2 Multidimensional Pseudo-Dynamic Arrays
        3. 9.1.6.3 Pure Dynamic Arrays
    2. 9.2 For More Information
  13. 10. String Data Types
    1. 10.1 Character String Formats
      1. 10.1.1 Zero-Terminated Strings
        1. 10.1.1.1 Utilize C Standard Library String Functions
        2. 10.1.1.2 When Not to Use Standard Library Functions
        3. 10.1.1.3 Avoid Recomputing Data
        4. 10.1.1.4 Avoid Copying Data
        5. 10.1.1.5 A Final Comment on Zero-Terminated Strings
      2. 10.1.2 Length-Prefixed Strings
      3. 10.1.3 7-Bit Strings
      4. 10.1.4 HLA Strings
      5. 10.1.5 Descriptor-Based Strings
    2. 10.2 Static, Pseudo-Dynamic, and Dynamic Strings
      1. 10.2.1 Static Strings
      2. 10.2.2 Pseudo-Dynamic Strings
      3. 10.2.3 Dynamic Strings
    3. 10.3 Reference Counting for Strings
    4. 10.4 Delphi/Kylix Strings
    5. 10.5 Using Strings in a High-Level Language
    6. 10.6 Character Data in Strings
    7. 10.7 For More Information
  14. 11. Pointer Data Types
    1. 11.1 Defining and Demystifying Pointers
    2. 11.2 Pointer Implementation in High-Level Languages
    3. 11.3 Pointers and Dynamic Memory Allocation
    4. 11.4 Pointer Operations and Pointer Arithmetic
      1. 11.4.1 Adding an Integer to a Pointer
      2. 11.4.2 Subtracting an Integer from a Pointer
      3. 11.4.3 Subtracting a Pointer from a Pointer
      4. 11.4.4 Comparing Pointers
      5. 11.4.5 Logical AND/OR and Pointers
      6. 11.4.6 Other Operations with Pointers
    5. 11.5 A Simple Memory Allocator Example
    6. 11.6 Garbage Collection
    7. 11.7 The OS and Memory Allocation
    8. 11.8 Heap Memory Overhead
    9. 11.9 Common Pointer Problems
      1. 11.9.1 Using an Uninitialized Pointer
      2. 11.9.2 Using a Pointer That Contains an Illegal Value
      3. 11.9.3 Continuing to Use Storage After It Has Been Freed
      4. 11.9.4 Failing to Free Storage When Done with It
      5. 11.9.5 Accessing Indirect Data Using the Wrong Data Type
    10. 11.10 For More Information
  15. 12. Record Union, and Class Data Types
    1. 12.1 Records
      1. 12.1.1 Record Declarations in Various Languages
        1. 12.1.1.1 Records in Pascal/Delphi
        2. 12.1.1.2 Records in C/C++
        3. 12.1.1.3 Records in HLA
      2. 12.1.2 Instantiation of a Record
      3. 12.1.3 Initialization of Record Data at Compile Time
      4. 12.1.4 Memory Storage of Records
      5. 12.1.5 Using Records to Improve Memory Performance
      6. 12.1.6 Dynamic Record Types and Databases
    2. 12.2 Discriminant Unions
    3. 12.3 Union Declarations in Various Languages
      1. 12.3.1 Union Declarations in C/C++
      2. 12.3.2 Union Declarations in Pascal/Delphi/Kylix
      3. 12.3.3 Union Declarations in HLA
    4. 12.4 Memory Storage of Unions
    5. 12.5 Other Uses of Unions
    6. 12.6 Variant Types
    7. 12.7 Namespaces
    8. 12.8 Classes and Objects
      1. 12.8.1 Classes Versus Objects
      2. 12.8.2 Simple Class Declarations in C++
      3. 12.8.3 Virtual Method Tables
      4. 12.8.4 Sharing VMTs
      5. 12.8.5 Inheritance in Classes
      6. 12.8.6 Polymorphism in Classes
      7. 12.8.7 Classes, Objects, and Performance
    9. 12.9 For More Information
  16. 13. Arithmetic and Logical Expressions
    1. 13.1 Arithmetic Expressions and Computer Architecture
      1. 13.1.1 Stack-Based Machines
        1. 13.1.1.1 Basic Stack Machine Organization
        2. 13.1.1.2 Pushing Data onto a Stack
        3. 13.1.1.3 Popping Data from a Stack
        4. 13.1.1.4 Arithmetic Operations on a Stack Machine
        5. 13.1.1.5 Real-World Stack Machines
      2. 13.1.2 Accumulator-Based Machines
      3. 13.1.3 Register-Based Machines
      4. 13.1.4 Typical Forms of Arithmetic Expressions
      5. 13.1.5 Three-Address Architectures
      6. 13.1.6 Two-Address Architectures
      7. 13.1.7 Architectural Differences and Your Code
      8. 13.1.8 Handling Complex Expressions
    2. 13.2 Optimization of Arithmetic Statements
      1. 13.2.1 Constant Folding
      2. 13.2.2 Constant Propagation
      3. 13.2.3 Dead Code Elimination
      4. 13.2.4 Common Subexpression Elimination
      5. 13.2.5 Strength Reduction
      6. 13.2.6 Induction
      7. 12.2.7 Loop Invariants
      8. 13.2.8 Optimizers and Programmers
    3. 13.3 Side Effects in Arithmetic Expressions
    4. 13.4 Containing Side Effects: Sequence Points
    5. 13.5 Avoiding Problems Caused by Side Effects
    6. 13.6 Forcing a Particular Order of Evaluation
    7. 13.7 Short-Circuit Evaluation
      1. 13.7.1 Short-Circuit Evaluation and Boolean Expressions
      2. 13.7.2 Forcing Short-Circuit or Complete Boolean Evaluation
      3. 13.7.3 Efficiency Issues
    8. 13.8 The Relative Cost of Arithmetic Operations
    9. 13.9 For More Information
  17. 14. Control Structures and Programmatic Decisions
    1. 14.1 Control Structures Are Slower Than Computations!
    2. 14.2 Introduction to Low-Level Control Structures
    3. 14.3 The goto Statement
    4. 14.4 break, continue, next, return, and Other Limited Forms of the goto Statement
    5. 14.5 The if Statement
      1. 14.5.1 Improving the Efficiency of Certain if/else Statements
      2. 14.5.2 Forcing Complete Boolean Evaluation in an if Statement
      3. 14.5.3 Forcing Short-Circuit Boolean Evaluation in an if Statement
    6. 14.6 The switch/case Statement
      1. 14.6.1 Semantics of a switch/case Statement
      2. 14.6.2 Jump Tables Versus Chained Comparisons
      3. 14.6.3 Other Implementations of switch/case
      4. 14.6.4 Compiler Output for switch Statements
    7. 14.7 For More Information
  18. 15. Iterative Control Structures
    1. 15.1 The while Loop
      1. 15.1.1 Forcing Complete Boolean Evaluation in a while Loop
        1. 15.1.1.1 The Easy but Inefficient Approach
        2. 15.1.1.2 Using Inline Functions
        3. 15.1.1.3 Using Bitwise Logical Operations
        4. 15.1.1.4 Using Unstructured Code
      2. 15.1.2 Forcing Short-Circuit Boolean Evaluation in a while Loop
    2. 15.2 The repeat..until (do..until/do..while) Loop
      1. 15.2.1 Forcing Complete Boolean Evaluation in a repeat..until Loop
      2. 15.2.2 Forcing Short-Circuit Boolean Evaluation in a repeat..until Loop
    3. 15.3 The forever..endfor Loop
      1. 15.3.1 Forcing Complete Boolean Evaluation in a forever Loop
      2. 15.3.2 Forcing Short-Circuit Boolean Evaluation in a forever Loop
    4. 15.4 The Definite Loop (for Loops)
    5. 15.5 For More Information
  19. 16. Functions and Procedures
    1. 16.1 Simple Function and Procedure Calls
      1. 16.1.1 Storing the Return Address
      2. 16.1.2 Other Sources of Overhead
    2. 16.2 Leaf Functions and Procedures
    3. 16.3 Macros and Inline Functions
    4. 16.4 Passing Parameters to a Function or Procedure
    5. 16.5 Activation Records and the Stack
      1. 16.5.1 Composition of the Activation Record
      2. 16.5.2 Assigning Offsets to Local Variables
      3. 16.5.3 Associating Offsets with Parameters
        1. 16.5.3.1 The Pascal Calling Convention
        2. 16.5.3.2 The C Calling Convention
        3. 16.5.3.3 Passing Parameters in Registers
      4. 16.5.4 Accessing Parameters and Local Variables
    6. 16.6 Parameter-Passing Mechanisms
      1. 16.6.1 Pass-by-Value
      2. 16.6.2 Pass-by-Reference
    7. 16.7 Function Return Values
    8. 16.8 For More Information
  20. A. Engineering Software
  21. B. A Brief Comparison of the 80x86 and PowerPC CPU Families
    1. A.1 Architectural Differences Between RISC and CISC
      1. A.1.1 Work Accomplished per Instruction
      2. A.1.2 Instruction Size
      3. A.1.3 Clock Speed and Clocks per Instruction
      4. A.1.4 Memory Access and Addressing Modes
      5. A.1.5 Registers
      6. A.1.6 Immediate (Constant) Operands
      7. A.1.7 Stacks
    2. A.2 Compiler and Application Binary Interface Issues
    3. A.3 Writing Great Code for Both Architectures
  22. C. Online Appendices
        1. Online Appendix B
        2. Online Appendix B
  23. Index
  24. Colophon
  25. D. Updates
  26. Copyright