Path: chuka.playstation.co.uk!news From: James Russell Newsgroups: scee.yaroze.beginners Subject: Object files and linking Date: Thu, 16 Jul 1998 21:57:54 +0100 Organization: Sony Computer Entertainment Europe Lines: 215 Message-ID: <35AE6952.2D94C5E9@scee.sony.co.uk> NNTP-Posting-Host: camfw01.millennium.co.uk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Mozilla 4.5b1 [en] (Win95; I) X-Accept-Language: en Not everybody here is a C guru, and I know the C book I learnt from (C programming for the Amiga, one of the dodgiest Abacus books around) wasn't exactly very helpful when it came to complex programs. So here's a little bit about object files and linking which hopefully everyone will find useful. When you first learn C, all of your programs will be in one source file, and you've probably learnt to compile it by some rote-learned wizard's incantation like "Abracadabra, ccpsx -o myCfile.exe myCfile.c -O3 -X$80100000" without really knowing what it all meant. If you've been programming for a little while and have started to create larger and larger programs, you've probably found that having all your source in one big file is a pain. It becomes difficult to manage and find the bits you want to adjust. Those marginally clued up will have figured out that '#include "myfile.c"' is just like inserting myfile.c into that point of the program, and will have split up their program that way (I know I did). So you'd have, say 5 separate C files that all got #include'd when you compiled, to make one big file and the magic incantation worked. Still, this is really inefficient, because the compiler has to re-compile the whole thing when you're probably only changing one line. The solution is to split up your program into various managable parts (say, Music, 2D graphics, Controllers, Main Loop, etc). These parts are then compiled into _object_files_, and the object files are linked together to form the final program. The beauty of this scheme is that once an object file has been created, you don't need to recompile it unless the related source code changes. You always need to relink all the object files together though. This means that you'll get much faster compilation/linking times, because the compiler doesn't have to process all the C source files, only the ones you've changed. === To create an object file, the magic incantation is: ccpsx -c -o myCfile.o myCfile.c '-c' means compile only, don't link. This will compile your file (turn it into machine code) but won't try to link it up with any other object files. '-o' means that the next argument is the name of the object file to create. It doesn't matter what it's called, but most people use the same name as the source file with the extension ".o" or ".obj" If you've split up your file up into lots of different chunks and tried the above, you've probably encountered lots of horrible errors. There's usually a common cause for all this, and they're remedied with ".h" files. Variables: When you compile a C file it's usually going to reference variables or functions in another C file. If you have the variable 'myVar' defined in another source file, and use "myVar = myVar + 1" in this file, then the compiler is going to have to know what sort of variable 'myVar' is. It could be a char, an int, a pointer, a float, or anything, so the compiler must know what type it is, otherwise it can't generate the correct machine code to process it. The way to let the compiler know that a variable is defined outside of this file is with the 'extern' keyword. If you specify 'extern float myVar;' before you use 'myVar' in this C file, then the compiler will know that myVar is referring to a float in some other C file somewhere. Functions: Functions defined outside the file don't need the 'extern' keyword. But they do need a _function_prototype_. A function prototype looks exactly like the first line of a function definition: e.g. int myFunction(int a, float b) { return a + b; } has a function prototype of: int myFunction(int a, float b); This is to let the compiler know how many parameters the external function will expect, what types those parameters will be, and what sort of variable it is going to return. If it didn't know these things, it couldn't create the appropriate machine code. This is why you #include , so that the compiler knows how many/what type the variables passed to/from library functions are. ".h" files. People tend to get a bit sick of writing 'extern' this and 'void myFunction(...);' prototypes in EVERY C file that references another object file. So they tend to create HEADER files, which contain all that guff, and then just #include it. So say you had a C file called "graphics.c" which contained all your useful graphics functions, you might create "graphics.h" which contained all of graphics.c's function prototypes, and usually any structure definitions used by graphics.c too. This graphics.h file can then be #include'd into your _other_ C files so that you don't have to define the function prototypes. ".h" files are used in many wonderful and wierd ways, so you may see variations on this. But their main purpose is to be included with C files so that the compiler will know the types, functions and variables involved. === Once your C file has been compiled into an object file, you need to link all the object files together to create a single executable. This is done with the program 'ld', the linker. ccpsx's default behaviour is to compile the file you give it and then call 'ld' on it, which is why just calling ccpsx will get you a real executable, because it's actually calling ld which does the executable creation. ccpsx is intelligent enough to distinguish between a C source file and an object file too, so if you call ccpsx with a list of object files, it will pass them all to the linker instead of trying to compile them. The linker takes all the object files and adjusts the machine code in each one so that the function calls and variable references work correctly. It's rare to get linker errors, but they can be troublesome because your code compiled fine (no C errors) but it won't link. The solution is usually to make sure that you've made no spelling mistakes when referencing external variables and functions (uppercase/lowercase matters too). Generally, a linker problem stems from one C file's idea of things being different from another C file's idea of things. So if you add a new parameter to a function, be sure to add the same parameter to the appropriate .h file. One of the parameters you specify to the linker is the link address - this is where the program will be placed when it is run. === Function/variable naming The object file contains the names of the functions and variables involved, so when the linker uses the object file it can figure out which functions 'connect'. Case is usually important in naming. The C keyword 'static' is useful here. If a global variable or function is defined 'static' then its name is effectively not exported to the object file. This means that no other object file can access that global variable/function. This is useful when you use a lot of global variables/functions in different C files with the same name, like 'timer' or 'count', or 'initialise()'. If you define these variables/functions as 'static', the names are not exported and so there won't be any conflicts when you link with another file using 'timer' or 'count' as a global variable/function name. === Including raw data Sometimes it is useful to include raw data (like a TMD or TIM) directly into the program. There are programs available which will convert raw data into a C source file (looking something like "char myRawData[] = { 0x12,0x34,0x56... };) If you compile these files into object files, then you can just link them in at linktime, instead of #include'ing them. This will drastically reduce compile times. All you need to do in your main program is have 'extern char myRawData[]' somewhere near the top. === ".lib" files A library is a collection of object files, all stored into one big file. You pass a .lib file to the linker as normal, but it only extracts the object files it needs. This is really useful when you've created a generic library of functions and you want to pass it around to other developers or use it in different programs. Since only the object files containing functions that are used are included in the final executable, this is a drastic reduction in file size than if you'd included everything. === Makefiles Makefiles are initially difficult to create and understand, but in essence they are very simple. When you make a program using lots of object files, you don't want to have to type in that long magic incantation to compile the ones you've changed. The make program can detect which files have been adjusted and call ccpsx/ld for you. So when you quit your editor, ready to compile, make will only find and compile _only_the_adjusted_ files, so all you need to do is type 'make' regardless of what C file you were editing. A makefile is a list of dependencies like: bananas.o: bananas.c bananas.h ccpsx -c -o bananas.o bananas.c This means "If bananas.h OR bananas.c is younger than bananas.o then run 'ccpsx -c -o bananas.o bananas.c'". If you have 3 object files "graphics.o", "music.o", "main.o" then your makefile might look like: myprog.exe: graphics.o music.o main.o ccpsx -o myprog.exe graphics.o music.o main.o graphics.o: graphics.c graphics.h ccpsx -c -o graphics.o graphics.c music.o: music.c music.h ccpsx -c -o music.o music.c main.o: main.c main.h ccpsx -c -o main.o main.c This is an oversimplification - makefiles are extremely powerful and can get extremely complicated. There were some tricks here I didn't use for simplicity of explanation. === Cheers, James -- == James_Russell@scee.sony.co.uk +44 (171) 447-1626 == Developer Support Engineer - Sony Computer Entertainment Europe Carpe Noctem.