- Use generation functions instead of data tables where possible
- Disable inline functions
- Turn frequently used macros into functions
- Reduce resolution for variables larger than the native machine size (ie, 8 bit micro, try to get rid of 16 and 32 bit variables - doubles and quadruples some code sequences)
- If the micro has a smaller instruction set (Arm thumb) enable it in the compiler
- If the memory is segmented (ie, paged or nonlinear) then
- Rearrange code so that fewer global calls (larger call instructions) need to be used
- Rearrange code and variable usage to eliminate global memory calls
- Re-evaluate global memory usage - if it can be placed on the stack then so much the better
- Make sure you're compiling with debug turned off - on some processors it makes a big difference
- Compress data that can't be generated on the fly - then decompress into ram at startup for fast access
- Delve into the compiler options - it may be that every call is automatically global, but you might be able to safely disable that on a file by file basis to reduce size (sometimes significantly)
Generally: make use of your linker map or tools to figure out what your largest/most numerous symbols are, and then possibly take a look at them using a disassembler. You'd be surprised at what you find this way.
Refactoring out duplicate code should have the biggest impact on your program's memory footprint.
Pay attention to macros. They can produce a lot of code from just one macro expansion. If you find such macros - try to rewrite them so that their size is minimized and functionality is moved to functions.
C51
The most significant impact on code size and execution speed is the selected memory model. Compiling in the small model always generates the smallest, fastest code possible.
The SMALL directive instructs the compiler to use the small memory model. In this memory model, all variables are stored in the internal memory of the 8051 (unless they are explicitly located elsewhere).
Memory access to internal data memory is very fast (typically performed in 1 or 2 clock cycles), and the code generated is much smaller than that generated for the compact or large models.
When possible, use local variables for loops and other temporary calculations. As part of the optimization process, the compiler attempts to maintain local variables in registers. Register access is the fastest type of memory access. The best effect is normally achieved with unsigned char and unsigned int variable types.
The 8051 family of processors does not specifically support operations with signed numbers. The compiler must generate additional code to deal with sign extensions. Far less code is produced if unsigned objects are used wherever possible.
Members of the 8051 family are all 8-bit MCUs. Operations that use 8-bit types (like char and unsigned char) are much more efficient than operations that use int or long types. For this reason, always use the smallest data type possible.
The Cx51 Compiler directly supports all byte operations. Byte types are not promoted to integers unless required. Refer to the INTPROMOTE directive for more information.
An example may be illustrated by examining a multiplication operation. The multiplication of two char objects is done inline with the 8051 instruction MUL AB. To accomplish the same operation with int or long types requires a call to a compiler library function.
Frequently accessed data objects should be located in the internal data memory of the 8051. Accessing the internal data memory is much more efficient than accessing external data memory. The internal data memory is shared among register banks, the bit data area, the stack, and other user-defined variables with the data memory type.
Because of the limited amount of internal data memory (128 to 256 bytes), all your program variables may not fit into this memory area. In this case, you must locate some variables in other memory areas. There are two ways to do this.
One way is to change the memory model and let the compiler do all the work. This is the simplest method, but it is also the most costly in terms of the amount of generated code and system performance. Refer to Memory Model for more information.
Another way to locate variables in other memory areas is to manually select the variables that can be moved into external data memory and declare them using the xdata memory specifier. Usually, string buffers and other large arrays can be declared with the xdata memory type without a significant degradation in performance or increase in code size.