practical c issues - university of...
Post on 04-Jun-2018
215 Views
Preview:
TRANSCRIPT
Practical C Issues:!Preprocessor Directives, Multi-file
Development, Makefiles
CS449 Fall 2017
Multi-file Development
Multi-file Development• Why break code into multiple source files?
– Parallel development involving multiple authors– Quicker compilation (only compile modified file)– Modularity (can reuse object file / library)– Encapsulation (easier to read / maintain)
• Use smallest scope to enforce encapsulation– Avoids polluting global namespace– Minimizes scope of code to find all uses of symbol
• But sometimes scopes must cross file boundaries...– Then same symbol must be declared in all relevant source files
Block / Function Scope
• Scope: Local (function or block of code)• Lifetime: Automatic (duration of function)
Static (duration of program)void foo(…) {
int x; // automatic
static int y; // static
x = …
y = …
}
Internal Linkage Scope
• Scope: File• Lifetime: Static (duration of program)static int x;
void foo(…) {
x = …
}
Void bar(…) {
x = …
}
External Linkage Scope
• Scope: Program (multiple files)• Lifetime: Static (duration of program)• extern used to declare vars in other filesFile Aint x;void foo() { … }
File Bextern int x;void foo();
B declares x and foo defined in A
Multi-file Example
a.cint x = 0; int f(int y) {
return x+y;
}
b.c#include <stdio.h> /* Declarations for symbols
defined in other files */ extern int x; int f(int); int main() { x = 1; printf("%d", f(2)); return 0; }
Multi-file Example
thoth$ gcc –c a.c b.c thoth$ gcc a.o b.o
thoth$ ./a.out
3
Pitfall: Missing Definitions
a.c// Missing ‘x’ definition int f(int y)
{
return y;
}
b.c#include <stdio.h> /* Declarations for symbols
defined in other files */ extern int x; int f(int); int main() { x = 1; printf("%d", f(2)); return 0; }
Pitfall: Missing Definitionsthoth$ gcc –c a.c b.c thoth$ gcc a.o b.o
./b.o: In function `main':
b.c:(.text+0x6): undefined reference to `x'
collect2: ld returned 1 exit status
• Linker cannot find ‘x’ in symbol table when it tries to resolve x in ‘x = 1;’ inside b.c
Pitfall: Missing Declarations
a.cint x = 0;
int f(int y)
{
return x+y;
}
b.c#include <stdio.h> /* Missing declarations for
x and int f(int) */ int main() { x = 1; printf("%d", f(2)); return 0; }
Pitfall: Missing Declarations
thoth$ gcc –c a.c b.c ./b.c: In function ‘main’:
./b.c:5: error: ‘x’ undeclared
…
• Compiler must know there is an externally defined ‘x’ and its type is ‘int’ to generate code
Declarations and Definitions• Definitions: Put into individual C (.c) files
– Function definitions: ends up in text section of .o file– Variable definitions: ends up in data section of .o file– One symbol must be defined in only one C file
• Declarations: Put into header (.h) filesè #included in any C file using the symbols– Function declarations– Variables declarations (using extern)– Type definitions (e.g. struct definitions)– #defines
Declarations and Definitions
mymalloc.h// Declarations
void *my_malloc(int size);
void my_free(void *ptr);
• Why wasn’t head declared in mymalloc.h?
mymalloc.c// Definitions
static void *head;
void *my_malloc(int size)
{
...
}
void my_free(void *ptr)
{
...
}
Including a Header File• Insert header In each C file using my_malloc, my_free:
main.c#include “mymalloc.h” // insert header int main() {
my_malloc(…); } foo.c #include “mymalloc.h” // insert header int foo() {
my_free(…); }
Compiling Multiple Files• Compiling:
gcc mymalloc.c main.c foo.c
• Why not also compile mymalloc.h?A. Header does not define any symbols
– Contains only declarations to help compiler– Nothing to generate an object file out of
(Code / data segment would be empty)– Hence nothing to compile
Makefiles:!Easy Multi-file Development
Makefile• A script interpreted by the GNU Make utility to build
projects containing multiple files• Design Goal: on a source / header file modification,
perform the smallest set of actions required– By expressing what files depend upon others
• Composed of a collection of rules which look liketarget:dependencies
action– That is a single <tab> before action, not spaces!
• Script invoked by: make<target>– If no target given, first target in script built by default
Makefilemalloctest: mymalloc.o mallocdriver.o
gcc –o malloctest mymalloc.o mallocdriver.o
mymalloc.o: mymalloc.c mymalloc.h
gcc –c mymalloc.c
mallocdriver.o: mallocdriver.c mymalloc.h gcc –c mallocdriver.c
clean:
rm –f *.o malloctest
Dependency Graph
malloctest
mymalloc.o mallocdriver.o
mymalloc.hmymalloc.c mallocdriver.c
Using a Makefile
thoth$lsMakefile mallocdrv.c mymalloc.c mymalloc.h thoth$makegcc -c mymalloc.c gcc -c mallocdrv.c gcc -o malloctest mymalloc.o mallocdrv.o thoth$makemake: `malloctest' is up to date.
• Build from scratch
• Partial build after modifying mymalloc.cthoth$touchmymalloc.cthoth$makegcc -c mymalloc.c gcc -o malloctest mymalloc.o mallocdrv.o
Defining Variables in Makefiles
• Works like macros (text replacement)• Syntax: <name> := ... or <name> = ...• Example:
– Instead of:malloctest: mymalloc.o mallocdriver.o
gcc –o malloctest mymalloc.o mallocdriver.o
– Can do:OBJECTS = mymalloc.o mallocdriver.o
malloctest: $(OBJECTS)
gcc –o malloctest $(OBJECTS)
Automatic Variables
• $@: The file name of the target. E.g.:malloctest: $(OBJECTS)
gcc –o $@ $(OBJECTS)
• $<: The name of the first prerequisite. E.g.:mymalloc.o: mymalloc.c mymalloc.h gcc –c $<
• $^: The names of all prerequisites. E.g.:malloctest: $(OBJECTS)
gcc –o $@ $^
Pattern Matching• Character ‘%’ is a wildcard for any string• Example:%.o: %.c
gcc –c $< -o $@
• What it means:– For all targets matching <some string>.o– Dependency is <that string>.c– Action is gcc –c <that string>.c –o <that string>.o
• Rule is used to produce any .o file from .c file
Concise Makefilemalloctest: mymalloc.o mallocdriver.o
gcc -o $@ $^
%.o: %.c
gcc -c $< -o $@
mymalloc.o: mymalloc.h mallocdriver.o: mymalloc.h
clean:
rm -f *.o malloctest
Make Utility Options• Usage:
make [-f makefile] [options] [targets]
• -f makefile: Makefile to interpret• targets: Targets to be built• Options:
– <name> = <value>: Define a variable.– -C <dir>: Change to directory <dir> before building.– -n: Dry run. Just print commands and don’t execute.– -d: Debug mode. Print verbose information.
Preprocecessor Directives:!Easy Manipulation of Source Code
#include• Copies the contents of specified file into current file• #include <…>: file in standard include location
– Usually /usr/include • #include “ ”: file in current directory or specified paths
– Paths specified using the –I option• If main.c has following includes:#include <stdio.h> #include “myheader.h”
… and is compiled using gcc –I ~/local/include main.c – stdio.h under /usr/include – myheader.h under current directory or ~/local/include
#define
• Defines macros– Macro: rule that specifies textual replacement
of one string for another• Often used to assign names to constants#define PI 3.1415926535 #define MAX 10
float f = PI; for(i=0;i<MAX;i++) …
#define• Good macros are generic
(do not make assumptions about inputs)• Good:
– #define MAX(a,b) (a > b) ? a : b – Only assumes ‘a’ can be compared to ‘b’
• Not so good:– #define SWAP(a,b) {int t=a; a=b; b=t;} – Makes assumption that types are ‘int’
• Better– #define SWAP(T,a,b) {T t=a; a=b; b=t;}
#if• #if <condition known to preprocessor>
// Some code #endif– Preprocessor emits code between #if directive and
#endif directive to the compiler only if condition is true– Condition evaluated at preprocessing time
(cf. C if statement is evaluated at execution time)• What does preprocessor know?
– Constants (0, 1, 2, “Linux”, “x86”, …)– Values of #defined variables– Arithmetic (+, -, *, /, >, <, ==, &&, ||, …)
Example#include <stdio.h> int main()
{ #if 0
printf(“This is not compiled\n”);
I can doodle here when I am bored. #endif
printf(“This is compiled\n”); return 0;
}
Example 2#include <stdio.h> #define LIBRARY_VERSION 7
int main() {
#if LIBRARY_VERSION >= 5
some_function_included_in_version_5(); printf(“This is compiled\n”);
#endif return 0;
}
#else#if <condition1>
// Emitted if condition1 is true…
#elif <condition2>// Emitted if condition1 is false and condition2 is true…
#else// Emitted if neither is true…
#endif
#if defined
• #if defined– Checks to see if a macro has been defined,
but doesn’t care about the value– A defined macro might expand to nothing, but
is still considered defined
Example#include <stdio.h>
#define DEBUG
int main()
{
#if defined DEBUG
printf(“Some debug output\n”); #endif
…
return 0;
}
#undef• Undefines a macro:
#include <stdio.h>
#define DEBUG int main() {
#if defined DEBUG
printf(“This is printed\n”);
#endif
#undef DEBUG #if defined DEBUG
printf(“This is not\n”);
#endif
return 0; }
Shortcuts for #if defined
• #if defined → #ifdef• #if !defined → #ifndef
Uses of #if directive• Enable / disable code specific to a library, OS, CPU, etc.• Turn on / off different features of program
– Debugging: #ifdefDEBUG
printf(…)#endif– More flexible debugging//easiertomodifyfunctionalityofPrintDebuglater#ifdefDEBUG#definePrintDebug(args…)fprintf(stderr,args)#else#definePrintDebug(args…)#endif
Using #if to #include!Header Only Once
• Including same header twice can lead to compile errors– Redefinition of the same struct type, etc..– Easy to stumble into with multiple levels of nested headers
#ifndef _MYHEADER_H_ #define _MYHEADER_H_
…Declarations only to be included once
#endif
Command Line Defined Macros
• –D: Defines variables from command line – gcc –o test –DVERSION=5 test.c – gcc –o test –DDEBUG test.c
• Allows tailoring of source code to specific versions and features from command line
Pre-Defined MacrosMacro Meaning__FILE__ Current compiled file__LINE__ Current line number__DATE__ Current date__TIME__ Current time__STDC__ Defined if compiler supports ANSI C__GNUC__ Defined if compiler is GNU C compiler__VERSION__ Version of GNU C compiler__unix__ Defined if OS is a UNIX compliant system__i386__ Defined if CPU has x86 instruction set… Many other macros
Other Preprocessor Details• # - places quotes around macro argument
– #define CALL(f) { printf(#f); f(); } – CALL(foo) → { printf(“foo”); foo(); }
• ## - concatenates two things in macro– #define CALL(f) f ## _debug () – CALL(foo) → foo_debug()
• #error: Emit error message and exit• #warning: Emit warning message• #pragma: Change behavior of compiler
ErrorDirec4veExample#include<stdio.h>#ifndef__i386__#error"Needsx86architecture."#endifintmain(){//Somex86specificcodereturn0;}
>>gcc./error.c./error.c:3:2:error:#error"Needsx86architecture.>>gcc–m32./error.c
• TestswhetherhardwareplaGormisi386(x86)anddisplayserror
• Ini4allyfailsbecausedefaultcompila4ontargetisx86_64(and__i386__isnotdefined)
• ‘-m32’op4onchangestargettox86,implicitlydefining__i386__
• #warning: allowscompila4on but withwarningmessage
Concatena4onExample#include<stdio.h>#ifdef__i386__#defineCALL(f)f##_x86()#else#defineCALL(f)f##_x86_64()#endifvoidfoo_x86(){printf(”x86binaryexecutable\n");}voidfoo_x86_64(){printf(”x86_64binaryexecutable\n");}intmain(){CALL(foo);return0;}
>>gcc./concat.c>>./a.outx86_64binaryexecutable>>gcc–m32./concat.c>>./a.outx86binaryexecutable
• Callsdifferentfunc4onsdependingontargetarchitecture
• When__i386__macroisundefined:CALL(foo)callsfoo_x86_64()
• When__i386__macroisdefined:CALL(foo)callsfoo_x86()
• ‘-m32’op4onimplicitlypasses‘-D__i386__’topreprocessor
PragmaExample#include<stdio.h>#pragmamessage"Compiling"__FILE__"
using"__VERSION__intmain(){return0;}
>>gcc./pragma.c./pragma.c:3:note:#pragmamessage:Compiling./pragma.cusing4.4.720120313(RedHat4.4.7-4)
• Printsdiagnos4cmessageduringcompila4onoffile
• Notetwopre-definedmacros:__FILE__and__VERSION__
• Manymorepragmas– Tocontrolstructpadding
(e.g.“#pragmapack(1)”)– Tocontrolcodeop4miza4ons
(e.g.“#pragmaompparallel”)
top related