This article provides an overview of the GNU Compiler Collection (GCC) from the Free Software Foundation and its use on Mac OS X. GCC is a free software project that has been used for many years to create software for UNIX and other platforms. Apple's developer tool Xcode (and previously, Project Builder) uses GCC under the hood for building executable images from source code. The GNU Debugger (GDB), a companion to GCC, comprises the foundation of the Xcode debugger. The examples presented here are command-line driven for several reasons:
How GCC WorksGCC consists of a set of tools that may be invoked using a single command plus options. True to the UNIX philosophy, the GCC tools perform specialized functions but work in harmony, passing the output from one tool as input to the next. GCC invokes these tools in a series of stages: compiler, assembler, and linker.
Source and header files may be any of several types, most commonly .c, .cc, .cp, .m, .mm, and .h on Mac OS X. GCC supports a variety of languages and extensions denoting source files, precompiled headers, source files not requiring preprocessing, and so on. Refer to the GCC manual for more information. A link is provided at the end of this article. Apple's version of GCC is based on standard GCC releases and adds features that support Mac OS X. Some of these features get folded in to the standard releases. Since GCC is an open source project it depends on its developer community for enhancements and fixes. Developers are encouraged to participate so that GCC can continue to evolve to support new languages, new processors, better optimization techniques, and so on. A Simple ExampleThe Carbon Example (52KB) illustrates compiling, linking, and running a simple application that has no user interface but does use a timer task. It installs the task in the Time Manager queue. Every second the task triggers and invokes a TimerUPP, or callback function, defined in our source code. The callback simply logs the current time to stdout.
The
This eases the burden on the programmer: rather than hunt down individual header files, you only need to include one framework/header combination. You can include other frameworks as needed. The top of the file contains declarations for several global variables, including the timer proc that gets installed in the Time Manager queue. Once the application runs its course (five iterations), it disposes of the timer proc rather than leave it installed.
The timer proc (
You could write this application to instead provide a user interface with windows, menus, and so on. Simply include the appropriate frameworks in the source code files. #include <CoreServices/CoreServices.h> void MyInit( void ); void MyTimerProc( TMTaskPtr tmTaskPtr ); Boolean gQuitFlag = false; int gCount = 0; TimerUPP gMyTimerProc = NULL; int main( int argc, char *argv[]) { MyInit(); while ( false == gQuitFlag ) { ; } DisposeTimerUPP( gMyTimerProc ); return 0; } void MyTimerProc( TMTaskPtr tmTaskPtr ) { DateTimeRec localDateTime; GetTime( &localDateTime ); printf( "MyTimerProc at %d:%d:%d\n", localDateTime.hour, localDateTime.minute, localDateTime.second ); gCount++; if ( gCount > 4 ) { gQuitFlag = true; } else { PrimeTimeTask( ( QElemPtr ) tmTaskPtr, 1000 ); } } void MyInit( void ) { struct TMTask myTask; OSErr err = 0; gMyTimerProc = NewTimerUPP( MyTimerProc ); if ( gMyTimerProc != NULL ) { myTask.qLink = NULL; myTask.qType = 0; myTask.tmAddr = gMyTimerProc; myTask.tmCount = 0; myTask.tmWakeUp = 0; myTask.tmReserved = 0; err = InstallTimeTask( ( QElemPtr )&myTask ); if ( err == noErr ) PrimeTimeTask( ( QElemPtr )&myTask, 1000 ); else { DisposeTimerUPP( gMyTimerProc ); gMyTimerProc = NULL; gQuitFlag = true; } } }
Mac OS X frameworks allow you to add system features and user interface capabilities to your applications. The frameworks are arranged hierarchically, so you only need to include in your source code the top-level framework of interest, rather than sub-frameworks underneath. When invoking GCC, the % gcc -framework CoreServices -o test test.c
To run the executable, invoke it by name. This example runs the file % ./test MyTimerProc at 16:41:27 MyTimerProc at 16:41:28 MyTimerProc at 16:41:29 MyTimerProc at 16:41:30 MyTimerProc at 16:41:31 % Useful FlagsHere are brief descriptions of several GCC command-line options:
-framework CoreServices -framework Carbon
You can change compiler versions using sudo /usr/sbin/gcc_select <version: 2, 3 or 3.x> % sudo /usr/sbin/gcc_select 3.1 Default compiler has been set to: Apple Computer, Inc. GCC version 1256, based on gcc version 3.1 20021003 (prerelease) % The % gcc_select -l Available compiler versions: 2.95.2 3.1 3.3 3.3-fast % Run The compiler version matters because changes to the Application Binary Interface since Mac OS X v10.0 have rendered C++ and Objective-C++ executables incompatible with earlier releases. However, C and Objective-C programs still run the same. To build for 10.1 and earlier you must use version 2 of GCC for C++/Obj-C++ applications and kernel extensions; version 2.95.2 was the GCC final release that shipped with Mac OS X v10.1. GCC version 3.1 shipped with Mac OS X v10.2 Jaguar, and 3.3 with Mac OS X v10.3 Panther. Note that if you mix languages in the application you should rebuild using the appropriate compiler version: 2.95.2 for Mac OS X v10.0 and v10.1, 3.1 for v10.2 Jaguar, and 3.3 for v10.3 Panther.
This list is not exhaustive. Look at the MakefilesMakefiles help automate the build process. A makefile is typically named A makefile contains a set of targets. Each target may be dependent on other targets. Each target also includes a command-line invocation preceded by a <tab> character. Here is the syntax: # Comments begin with a '#'.
The following example incorporates the Where does # The target named test depends on target test.obj. # The command for target test: # 1. invokes gcc, # 2. generates a file named test (no extension) as output, and # 3. uses file test.o as input. # test: test.obj gcc -o test test.o # # The next target name is test.obj. That is only its name. # The name does not have to relate to what the target actually builds. # # This target builds object files from source. # The command for test.obj instructs gcc to: # 1. stop after compilation (no linking), # 2. print verbose output, and # 3. use the file test.c as input. # # The default output file name here will be test.o. # test.obj: test.c gcc -c -v test.c # # Remove unwanted binaries, both the file named test and any .o files. # clean: rm test *.o # # Generate assembly files. Useful for debugging. # asm0: gcc -v -S -O0 -o test.O0.s test.c asm1: gcc -v -S -O1 -o test.O1.s test.c asm2: gcc -v -S -O2 -o test.O2.s test.c asm3: gcc -v -S -O3 -o test.O3.s test.c You invoke the $ make test gcc -c -v test.c Reading specs from /usr/libexec/gcc/darwin/ppc/3.3/specs Thread model: posix gcc version 3.3 20030304 (Apple Computer, Inc. build 1640) /usr/libexec/gcc/darwin/ppc/3.3/cc1 -quiet -v -D__GNUC__=3 -D__GNUC_MINOR__=3 -D__GNUC_PATCHLEVEL__=0 -D__APPLE_CC__=1640 -D__DYNAMIC__ test.c -fPIC -quiet -dumpbase test.c -auxbase test -version -o /var/tmp//ccmr23gL.s GNU C version 3.3 20030304 (Apple Computer, Inc. build 1640) (ppc-darwin) compiled by GNU C version 3.3 20030304 (Apple Computer, Inc. build 1640). GGC heuristics: --param ggc-min-expand=30 --param ggc-min-heapsize=131072 ignoring nonexistent directory "/usr/local/include" ignoring nonexistent directory "/usr/ppc-darwin/include" ignoring nonexistent directory "/Local/Library/Frameworks" #include "..." search starts here: #include <...> search starts here: /usr/include/gcc/darwin/3.3 /usr/include End of search list. Framework search starts here: /System/Library/Frameworks /Library/Frameworks End of framework search list. /usr/libexec/gcc/darwin/ppc/as -arch ppc -o test.o /var/tmp//ccmr23gL.s gcc -o test test.o $ If the make script is stored in a file named something other than make -f test.mk test GNU DebuggerThe GNU debugger (GDB) allows you to step through code, watch values, and monitor execution from the command-line. Xcode uses GDB as the basis for its debugger. If you are using GCC you will want to also learn to use GDB.
The first step is to compile your code with the
You invoke GDB using the % gdb test GNU gdb 5.3-20030128 (Apple version gdb-309) (Thu Dec 4 15:41:30 GMT 2003) Copyright 2003 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "powerpc-apple-darwin". Reading symbols for shared libraries ... done (gdb) The best thing to start with is a breakpoint. This command sets a breakpoint on line 1. (gdb) break 1 Breakpoint 1 at 0x1bd8: file test.c, line 1. (gdb)
Begin execution by typing (gdb) run Starting program: /ktree/test [Switching to thread 1 (process 1254 thread 0x1603)] Reading symbols for shared libraries ......... done Breakpoint 1, main (argc=795571314, argv=0x65652f74) at test.c:12 12 { (gdb)
The (gdb) step MyInit () at test.c:47 47 OSErr err = 0; (gdb)
Use the (gdb) print err $1 = 0 (gdb)
Step over a function using (gdb) next main (argc=1, argv=0xbffffbb0) at test.c:13 13 MyInit(); (gdb) next 15 while ( false == gQuitFlag ) { (gdb) Next, set a breakpoint in the callback (line 28), then continue execution. GDB pauses when execution reaches the breakpoint. Check the date/time value. Note that GDB does its best when displaying the structure fields. The structure looks a bit strange, so check the data type of (gdb) break 28 Breakpoint 2 at 0x2b8c: file test.c, line 28. (gdb) continue Continuing. [Switching to process 1080 thread 0x1203] Breakpoint 2, MyTimerProc (tmTaskPtr=0xbffffd10) at test.c:28 28 GetTime( &localDateTime ); (gdb) print localDateTime $3 = { year = 0, month = 119, day = 25967, hour = 7018, minute = 0, second = 0, dayOfWeek = 0 } (gdb) whatis localDateTime type = DateTimeRec (gdb) Step through the next couple of lines and view the formatted output, which looks correct. (gdb) step 30 printf( "MyTimerProc at %d:%d:%d\n", localDateTime.hour, localDateTime.minute, localDateTime.second ); (gdb) step MyTimerProc at 14:45:21 32 gCount++; (gdb) Use the (gdb) where #0 MyTimerProc (tmTaskPtr=0xbffffd10) at test.c:32 #1 0x902be7e8 in TimerThread () #2 0x900246e8 in _pthread_body () (gdb) Change a variable value using (gdb) print gCount $4 = 0 (gdb) step 34 if ( gCount > 4 ) (gdb) print gCount $5 = 1 (gdb) set gCount = 5 (gdb) step 36 gQuitFlag = true; (gdb) continue Continuing. Program exited normally. (gdb) Here is the conventional way to stop debugging and exit GDB: (gdb) stop (gdb) quit OptimizationCode optimization may be performed by the programmer, the compiler, or the runtime environment. This section focuses on optimizations that GCC can perform at build time. The typical tradeoff is to choose smaller code size over faster execution speed, or vice versa. It is impossible to fully optimize for both at the same time, though GCC does its best, as do other compilers. When in doubt, it may be better to optimize for size, since smaller code may execute relatively faster. For example, large functions or loops containing data access patterns that do not exhibit a strong locality of reference may not fit into a processor's cache lines, which can lead to cache misses and subsequent fetches from memory. Smaller functions or loops with local data access stand a better chance of fitting in a given cache line and requiring fewer memory accesses. Another reason for looking at optimization settings is because often developers debug without optimization enabled, then release an optimized version to the public. Being familiar (not necessarily intimate) with the assembly listing of your program under various optimization settings may help you determine where execution failed when you receive the occasional crash report. Remember that optimized code typically bears little resemblance to the original source code. This makes it difficult to look at an assembly listing for an optimized program and determine the flow of control. It can be nearly impossible to to look at a crash log and determine the point in an optimized program where a problem occured, unless you have symbols included, which is not typically the case. Unoptimized code follows the original source code directly, making it easier to debug. In fact, you will have better luck first debugging the code and then optimizing it, rather than the other way around. Trying to do both simultaneously is also a bad idea. You can use the gcc -S -O0 -o test.O0.s test.c Several of the common options are discussed here. The GCC manual contains additional information regarding optimization settings. The source code and assembly listings are available in the Optimization Example (88KB) folder. Optimization levels and modes
For example, this source code generates the accompanying machine instructions under Level 0 and Level 2: unsigned int doWhileWithReturn( void ) { unsigned int i = 100; unsigned int result = 0; unsigned int a = 31, b = 2, c = 99; do { result += a * b; c = a * b; } while ( i-- > 0 ); c = a * b; return result; } Here is the unoptimized code (the _doWhileWithReturn: stmw r30,-8(r1) ; save non-volatile registers stwu r1,-80(r1) ; SP update mr r30,r1 ; i li r0,100 stw r0,32(r30) li r0,0 ; result stw r0,36(r30) li r0,31 ; a stw r0,40(r30) li r0,2 ; b stw r0,44(r30) li r0,99 ; c stw r0,48(r30) L16: lwz r2,40(r30) ; load a into r2 lwz r0,44(r30) ; load b into r0 mullw r2,r2,r0 ; multiply a and b, store in r2 lwz r0,36(r30) ; load result add r0,r0,r2 ; add product to result stw r0,36(r30) ; store new value of result lwz r2,40(r30) lwz r0,44(r30) mullw r0,r2,r0 ; multiply a and b, store in r0 stw r0,48(r30) ; store new value of c lwz r2,32(r30) ; load i addi r0,r2,-1 ; subtract 1 from i mr r2,r0 stw r2,32(r30) ; store i li r0,-1 cmpw cr7,r2,r0 ; compare i to -1, update condition register bne cr7,L16 ; loop if i > 0 (branch to label l16) lwz r2,40(r30) lwz r0,44(r30) mullw r0,r2,r0 ; multiply a and b, store in r0 stw r0,48(r30) ; store new value of c lwz r0,36(r30) ; Load result mr r3,r0 ; Move result to r3 lwz r1,0(r1) ; Restore SP lmw r30,-8(r1) ; Restore registers blr ; Return The _doWhileWithReturn: li r0,101 ; Load count register with 101 mtctr r0 L21: ; Loop has been almost completely optimized away bdnz L21 ; Decrement count register and branch to label L21 ; if not zero li r3,6262 ; Load result value of 6,262 into r3 blr Level 3GCC applies additional optimizations including implicit inlining, or inlining of functions not marked with the keyword FastThis mode, invoked by
This setting aggressively inlines functions. For example, here is a int main( void ) { unsigned int result; double doubleResult; arrayAssignmentLoop(); result = doWhileWithReturn(); printf( "doWhileWithReturn returned %d\n", result ); doubleResult = doubleTest(); printf( "doubleTest returned %lf\n", doubleResult ); return 0; } The unoptimized version calls each function: _main: mflr r0 stmw r30,-8(r1) stw r0,8(r1) stwu r1,-96(r1) mr r30,r1 bcl 20,31,"L00000000001$pb" "L00000000001$pb": mflr r31 bl L_arrayAssignmentLoop$stub ; Branch to arrayAssignmentLoop bl L_doWhileWithReturn$stub ; Branch to doWhileWithReturn mr r0,r3 stw r0,64(r30) addis r3,r31,ha16(LC0-"L00000000001$pb") la r3,lo16(LC0-"L00000000001$pb")(r3) lwz r4,64(r30) bl L_printf$stub bl L_doubleTest$stub ; Branch to doubleTest ...
The _main: mflr r2 li r3,2 ; Begin inlined and unrolled arrayAssignmentLoop li r11,10 li r10,9 li r9,8 li r8,7 li r7,6 li r6,5 li r5,4 stw r2,8(r1) stwu r1,-128(r1) li r4,3 stw r3,96(r1) stw r11,64(r1) stw r10,68(r1) stw r9,72(r1) stw r8,76(r1) stw r7,80(r1) stw r6,84(r1) stw r5,88(r1) stw r4,92(r1) ; End of arrayAssignmentLoop li r3,1 li r2,99 stw r3,100(r1) .p2align 4,,15 L149: ; Top of loop for doWhileWithReturn cmpwi cr0,r2,3 addi r2,r2,-4 bne cr0,L149 ; Branch to top of loop lis r5,ha16(LC1) li r4,6262 ; Result of doWhileWithReturn la r3,lo16(LC1)(r5) bl L_printf$stub bl _doubleTest ; Branch to doubleTest ... SizeYou can force smaller code size using the
This example compares double doubleTest( void ) { const unsigned int limit = 100; double array[ limit ][ limit ], sum = 0; unsigned int i, j; for ( i = 0; i < limit; i++ ) { for ( j = 0; j < limit; j++ ) { array[ i ][ j ] = i; sum += array[ i ][ j ]; } } return sum; }
Under _doubleTest: ... L117: ; Top of outer loop rldicl r5,r11,0,32 li r9,0 add r2,r10,r8 std r5,32(r30) lfd f2,32(r30) fcfid f0,f2 .p2align 4,,15 L116: ; Top of inner loop fadd f11,f1,f0 addi r9,r9,10 stfd f0,0(r2) stfd f0,8(r2) stfd f0,16(r2) stfd f0,24(r2) cmplwi cr0,r9,99 stfd f0,32(r2) stfd f0,40(r2) stfd f0,48(r2) stfd f0,56(r2) stfd f0,64(r2) stfd f0,72(r2) addi r2,r2,80 fadd f10,f11,f0 fadd f9,f10,f0 fadd f8,f9,f0 fadd f7,f8,f0 fadd f6,f7,f0 fadd f5,f6,f0 fadd f4,f5,f0 fadd f3,f4,f0 fadd f1,f3,f0 ble cr0,L116 ; Branch to top of inner loop addi r11,r11,1 addi r10,r10,800 cmplwi cr1,r11,99 ble cr1,L117 ; Branch to top of outer loop ...
Under _doubleTest: ... L41: ; Top of outer loop stw r9,36(r30) li r8,100 stw r10,32(r30) mtctr r8 lfd f0,32(r30) add r2,r11,r0 fsub f0,f0,f13 L46: ; Top of inner loop stfd f0,0(r2) ; Store followed by add fadd f1,f1,f0 addi r2,r2,8 bdnz L46 ; Branch to top of inner loop addi r9,r9,1 addi r11,r11,800 cmplwi cr7,r9,99 ble+ cr7,L41 ; Branch to top of outer loop ... Specific optimizations
Additional optimization settings are described in the GCC manual. For More InformationThis article touched on the fundamentals of GCC, optimization,
Also try these non-ADC resources:
If you want to keep up with GCC on other platforms try the GCC home page. Keep in mind that Apple-specific builds and docs are not available on this site. Posted: 2004-07-12 |