Comparison of Hi-Tech PICC-18 and MPLAB C18


This comparison was created while developing some software for a commercial application that needed to build with both C18 and PICC-18. Implementation details of each compiler are discussed, followed by some code size benchmarks. The latest full commercial release of each compiler as of April, 2004 was used: V8.30 of Hi-Tech PICC-18 and V2.30 of Microchip MPLAB C18.

ANSI/ISO compliance


While both compilers are substantially ANSI/ISO (C90) compliant, neither is 100% compliant. Divergences are noted in each compiler's users' manual:

  • Does not support recursion.
  • Has a non-standard implementation of string constants. The type of a string constant in PICC-18 is const char[]. The consequences of this are that a char pointer cannot point to a string constant; a const char pointer is required.

  • Has a non-standard implementation of string constants. The type of a string constant in C18 is const rom char[], which is compatible with neither const char[] nor char[]. This means that, for example, you cannot pass a string constant to a function such as strcpy which accepts a const char * parameter.
  • Does not support integer promotions by default. Integer promotions can be enabled with a command line switch, but doing so adds significantly to code size.
  • An integer constant is of type char if a char can represent the constant. This combined with lack of integer promotions means that, for example, the constant expression 16 * 16 would evaluate to zero.
  • By default, does not zero unitialized objects of static storage duration. This behavior can be enabled by linking in a different startup module. This startup module unconditionally initializes banks 0 through 14, regardless of whether any variables appear in these banks or whether RAM is even implemented in these banks for the PIC being used.


PICC-18 includes a full C library, excluding memory allocation functions. C18 includes the string.h, setjmp.h, and limited stdlib.h functions, but no stdio.h, time.h nor math.h functions. C90 does not require a freestanding implementation to include any library functions, so any omissions here do not violate the standard.

Implementation Differences

PICC-18 places objects declared const in ROM. C18 places const objects in RAM, unless they are also qualified with the rom keyword.

PICC-18 allows RAM objects of any size to be declared, though some limitations exist that require balancing objects between C source files in certain cases. C18 does not support RAM objects larger than 256 bytes by default; creating such objects requires editing linker control files and adding pragmas to the C source which include hard-coded variable addresses. These objects can only be accessed through pointers, not directly.

C18 has 32-bit floats, doubles, and long doubles. PICC-18 has 24-bit floats, and either 24- or 32-bit doubles and long doubles, specified by a command line option.

PICC-18 performs sign-extension when right-shifting signed integers; C18 does not.

A char is unsigned on PICC-18 and signed on C18. The default behavior can be changed with a command line option on both compilers.

A PICC-18 pointer can point to both ROM and RAM objects; the appropriate address space is determined at runtime. C18 pointers can point to ROM or RAM, but not both. Pointers to near are optimized to eight bits on PICC-18; C18 uses a standard 16-bit pointer.

PICC-18 uses a compiled stack for function arguments and local variables. C18 uses a software stack, but an "overlay" model is available with a command line switch.

C18's startup code calls main(), using a hardware stack level; PICC-18 branches to main() without using a hardware stack level.

Upon returning from main(), PICC-18 jumps back to the startup code. C18 calls main() again, without running the startup code to initialize variables again.


Integer types

PICC-18 supports a bit type. The compiler will pack up to eight bit variables in a byte of RAM. The compiler takes care of the overhead of extracting bit variables efficiently at run time.

C18 supports a 24-bit short long type.

Interrupt handlers

PICC-18 uses the interrupt keyword to signify an interrupt handler:
void interrupt isr(void)
   /* ... */
C18 requires the use of a stub function with an assembly language directive, along with several #pragmas:
void isr(void)
   /* ... */

#pragma code high_vector=0x08

void interrupt_at_high_vector(void)
    _asm GOTO isr _endasm

#pragma code

#pragma interrupt isr
Both compilers support low- and high-priority interrupt handlers, and both compilers make use of the fast call stack.

PICC-18 handles ISR context saving automatically. C18 requires the programmer to be aware of certain conditions that require adding #pragma directives for a correct context save.

Access Memory

Both compilers use the near keyword to place an object in access memory. C18 additionally requires bracketing any near declarations with #pragmas.

Both compilers may place near variables at address zero. A pointer to such a variable will compare equal to a null pointer, violating the ANSI/ISO constraint that a pointer to an object may not compare equal to a null pointer.

Configuration Fuses

Each compiler provides a mechanism to embed the processor's configuration fuses into the generated hex file. PICC-18 allows each configuration word to be specified independently; e.g.:
Any unspecified words are left at their default values. C18 requires all words to be specified; e.g.:
             _PWRT_ON_2L & _BOR_OFF_2L, _WDT_OFF_2H,
             _STVR_OFF_4L & _LVP_OFF_4L & _DEBUG_OFF_4L,


Both compilers have support for binary constants declared with the 0b prefix.

Both compilers support C++/C99-style (//) comments.

Both compilers support inline assembly language.

Both compilers have macros which will insert common machine instructions such as sleep and clrwdt.

C18 supports anonymous structures and unions.

Both compilers support both 16- and 24-bit pointers to ROM. C18 uses the far keyword to denote a 24-bit pointer; PICC-18 requires making all pointers either 16- or 24-bit with command line options.

On processors which have an external memory bus, PICC-18 uses the far keyword to place a variable in the external address space. C18 requires editing linker files and using pragmas to locate varibles in external memory.

Both compilers have header files which define the PIC18 registers and register bits. PICC-18 allows individual bit names to be referenced directly (e.g., RA4); C18 uses a more verbose syntax (e.g., PORTAbits.RA4).

PICC-18 supports a persistent qualifier, which removes a variable from the standard zeroing during C startup. These variables will retain their values across most types of processor reset. Library routines are included to validate the state of persistent variables.

C18 includes an extensive library of functions that interface to the PIC's peripherals and also provide software implementations of peripherals such as a UART.

C18 allows the static qualifier to be applied to function parameters. This causes the parameter to be passed through global memory rather than on the software stack.

C18 has an overlay storage class which can be applied to local variables when operating in non-extended mode. overlay variables can only be used in non-recursive functions, and are statically allocated, much like a compiled stack.

Neither compiler supports bit-fields larger than eight bits.

PICC-18 generates errata NOPs on processors that require them.


PICC-18's command-line driver will invoke the compiler, assembler, and linker as necessary. This makes it possible to easily build a hex file with a single command; e.g.:
picc18 -18f452 file.c
will compile and link file.c and generate file.hex. With C18, each step must be invoked separately; e.g.:
mcc18 -p18f452 file.c
mplink file.o c:\mcc18\lkr\18f452.lkr -lc:\mcc18\lib -o file.hex
Both compilers can easily be used with a make utility, and both are integrated into Microchip's MPLAB IDE.

Both compilers can produce list files showing the assembly language generated for each line of C code. PICC-18 generates a symbolic list file for each C source file. C18 generates a single non-symbolic list file for the entire project. PICC-18's list file includes the preprocessed source code, while C18's is not preprocessed. Here is a comparison of listing file formats:


   115                           ;t.c: 8: c = a * b;
   116  000038  C0FC  F002          movff   _b,btemp+2
   117  00003C  C0FD  F003          movff   _b+1,btemp+3
   118  000040  C0FA  F000          movff   _a,btemp
   119  000044  C0FB  F001          movff   _a+1,btemp+1
   120  000048  EC2C  F000          call    awmul
   121  00004C  C004  F0FE          movff   btemp+4,_c
   122  000050  C005  F0FF          movff   btemp+5,_c+1
   127                           ;t.c: 15: func(10);
   128  000056  0E0A                movlw   10
   129  000058  DFE7                call    _func
0000e4   50e8     MOVF      0xe8,0x0,0x0       c = a * b;
0000e6   036a     MULWF     0x6a,0x1
0000e8   cff3     MOVFF     0xff3,0x6e
0000ea   f06e
0000ec   cff4     MOVFF     0xff4,0x6f
0000ee   f06f
0000f0   036b     MULWF     0x6b,0x1
0000f2   50f3     MOVF      0xf3,0x0,0x0
0000f4   276f     ADDWF     0x6f,0x1,0x1
0000f6   516d     MOVF      0x6d,0x0,0x1
0000f8   036a     MULWF     0x6a,0x1
0000fa   50f3     MOVF      0xf3,0x0,0x0
0000fc   276f     ADDWF     0x6f,0x1,0x1
0000fe   0e0a     MOVLW     0xa                func(10);
000100   6ee6     MOVWF     0xe6,0x0
000102   dfe3     RCALL     0xca
Both compilers can produce linker map files. The C18 map file provides information about each code section, total ROM/RAM size, and a list of symbols sorted by both name and address. The PICC-18 map file shows the actual linker command line used, the call graph for the compiled stack, information about sections, information about code size and location in each object file, and a list of symbols sorted by either name or address, but not both. PICC-18 displays information about total ROM/RAM size on the console when linking.

PICC-18 supports both Windows and Linux; C18 supports only Windows.

Code Size Comparisons

Commercial Application

This comparison was made using software developed for an actual commercial project. The code was developed so that it would compile on both PICC-18 and C18. The target was an 18F part which supported the extended instruction set.

The target hardware consists of what I would consider a "typical" PIC application, using the A/D converter, UART, MSSP, timer 1 RTC, LCD, and varous switches, buttons, and LEDs.

The software is built on a generic state machine/event scheduler. It contains no floating point, but does include 16-bit integer multiplies and divides. No "hardware library" functions of either compiler are used.

On this particular CPU, enabling extended mode results in there being no access RAM available. Since the code does make use of "near" variables for reduced code size, necessary #ifdefs were included to allow the code to be built under C18 with near variables in standard mode as well as without near variables in extended mode. Results are reported for both compilation methods. PICC-18 does not support extended mode, and would probably not benefit from it, since it does not have a software stack.

The -O+ optimization flag was used for C18; the -O -Zg1 flags were used for PICC-18.

After the initial low-level driver code was developed and integrated into the state machine engine/event scheduler, the code size looked like this:

Lines of code: 2200
MPLAB C18 Hi-Tech PICC-18
Extended mode without near variables4243 bytes
Standard mode with near variables4197 bytes3246 bytes

The C18 code was 29% larger than the PICC-18 code.

In order to determine if the overhead of the software stack was responsible for the large disparity in code sizes generated by the two compilers, an effort was made to build the code under C18 with the -sco command line option. This option effectively enables a compiled stack by using static allocation for both function parameters and local variables. Unfortunately, the code would not compile with this option due to its use of function pointers. The user's guide suggested declaring function pointer arguments as auto to get around this problem. Doing so allowed the code to compile, but the link failed with "Error - higher order function calls not supported yet in the presence of the overlay storage class".

The code which used function pointers was then removed, and the -sco build completed successfully. The resulting C18 code size was 3435 bytes vs. PICC-18's 3108 bytes. The code size difference was reduced to 11%, which indicates the price of the software stack is very high.

This code is still in progress; this information will be updated as the code gets larger.

Microchip TCP/IP Stack

Microchip offers free C source code for a TCP/IP stack which includes web and FTP servers. The code was designed to be compiled with both PICC-18 and C18. Version 2.11 of this code was compiled for an 18F452. The C18 compilation required the -sco option due to a 128-byte auto array used in a function. The -NOERRATA option was used with PICC-18 to suppress errata NOPs which are not generated by C18. Maximum optimizations were used on both compilers. The 18F452 does not support extended mode (which cannot be used with -sco in any event), so extended/standard mode comparisons were not made.

Lines of code: 13500
Compiler Code size (bytes)
MPLAB C1823531
Hi-Tech PICC-1829358

The PICC-18 code was 25% larger than the C18 code. Surprised at this significant difference in results relative to the previous comparison, I investigated which C18 optimization was providing such a substantial reduction in code size. Disabling the procedural abstraction optimization changed the results to:

Lines of code: 13500
Compiler Code size (bytes)
MPLAB C1828891
Hi-Tech PICC-1829358

The size reduction due to procedural abstraction was 19%. I repeated this test on the commercial application and found the size reduction due to procedural abstraction was only 11%. Further investigation showed significant amounts of repetitious code appeared in the software. Some of the repetition was not obvious: e.g., heavy use of structure pointers which generate a tremendous amount of code from a small amount of C. Procedural abstraction proved very advantageous here.

The TCP/IP stack code contains support routines designed to support pointer limitations in C18 (e.g., XLCDPutROMString) which aren't necessary under PICC-18, but are compiled in anyway. As a result, PICC-18 is at a small disadvantage in code size.

Floating Point

I have never used floating point code on a PIC, though I have included a quick benchmark here for completeness. This is a completely artificial benchmark based on some code I saw posted on the Microchip forums. It uses only addition, subtraction, multiplication, and division, since C18 does not include a math library.
void main(void)
    double c, s, u, Ta, Tc, Tw;

    c = 18.0;
    s = 118.0;

    u = 1.2/c;
    Ta = 60.0/s - 307.2/c;
    Tc = Ta/179.0*38.0;
    Tw = Ta/197.0*7;
    u = .12/c + Tc;
    Ta = 6.00/s - 73.2/c;
    Tc = Ta/91.0*3.0;
    Tw = Ta/190.*7 + Tw;
    u = 1.12/c;
    Ta = 610.0/s - 3.72/c;
    Tc = Ta/19.0*3.10 - Tc;
    Tw = Ta/109.0*70 + Tw;
    u = 21.2/c;
    Tw = u + Ta + Tc;
The code was compiled with maximum optimizations on both compilers, using overlay mode on C18 and 32-bit doubles on PICC-18:

Compiler Code size (bytes)
MPLAB C184284
Hi-Tech PICC-181810

The C18 code was 137% larger than the PICC-18 code.

Copyright © 2004 John Temples (comparison at xargs dot com)