810 likes | 960 Views
A C++ Crash Course Part II. UW Association for Computing Machinery http://www.cs.washington.edu/orgs/acm/tutorials acm@cs.washington.edu. Questions & Feedback to Albert J. Wong (awong). C Preprocessor #include, #define, #if, etc. __FILE__, __LINE__ Multiple File Development Header Files
E N D
A C++ Crash CoursePart II UW Association for Computing Machinery http://www.cs.washington.edu/orgs/acm/tutorials acm@cs.washington.edu Questions & Feedback to Albert J. Wong (awong)
C Preprocessor #include, #define, #if, etc. __FILE__, __LINE__ Multiple File Development Header Files Separate Compilation Compilation Process Compilation Linking Advanced Modifiers const, static, extern, volatile Namespaces Operator Overloading Classes Constructors, Destructors Inheritance Virtual (dynamic dispatch) Dynamic Memory new, delete, delete[] malloc, calloc, realloc, free C/C++ I/O Functions What We Will Cover
What We Are NOT Covering • Anything in the first tutorial • Pointers, arrays, references • structs, unions, enums • Weird Inheritance • Multiple inheritance • Virtual inheritance (NOT dynamic dispatch) • Templates • Generic Programming • Exceptions • STL • Makefiles
Basics refresher Quiz • Write a struct called Point3D that represents a 3D point. Give it 3 member variables, x, y, z with type double. • Given that struct definition, and following code: • Count how many times a Point3D is created • List all places memory is allocated, and the type of memory allocated typedef struct Point3D Point3D; Point3D add(Point3D *a, Point3D *b) { Point3D p; p.x = a->x + b->x; p.y = a->y + b->y; return p } int main(void) { Point3D p1 = {0,1}; Point3D p2 = {3,4}; Point3D p3 = add(&p1, &p2); return 0; }
Declarations - (bad) Example What is wrong with the following code? Will it compile in C? in C++? int main(void) { int result; ... result = add(20, 3); ... return 0; } int add(int a, int b) { return a + b; } Hint: It has to do with the declaration and use of add.
add is declared in the shaded regions Declaration Regions Corrected Code 2 (global prototype) Corrected Code 1 (local prototype) int main(void) { int result; int add(int,int); ... result = add(20, 3); ... return 0; } int add(int a, int b) { return a + b; } ... int add(int,int); int main(void) { int result; ... result = add(20, 3); ... return 0; } int add(int a, int b) { return a + b; } ...
Dangerous! Declarations - Summary Declarations and definitions are separate concepts • Declarations can be separate from definitions • Function declarations are called prototypes • Declarations should be given before usage • C & C++ handle undeclared functions differently • In C++ this is an error • In C, the function is implicitly declared with the type: int name(...);
I/O C-style C-style IO is an acquired taste. Learn to like it. Basic functions: • printf, scanf, fprintf, fscanf, sprintf, sscanf, etc. • gets, puts, getc, putc, getchar • read, write, fread, fwrite • We will cover the basics of the “formated” family of functions (printf, scanf, etc). • For the others, read the man pages in Unix.
printf printf(char *format_string, ...); fprintf(FILE*, char *format_string, ...); snprintf(char* buf, size_t n, char *format_string, ...); • In C, all devices are treated like files • Three standard files are: • stdin Often the keyboard • stdout Often the text console • stderr Often the text console • printf(....) is fprintf(stdout, ....) • The format string is a pattern for the output; it describes how to display the arguments to printf. • Snprintf write to the string “buf”. The variable n specifies the size of the buffer. • printf returns the number of characters written
format string • Format strings are normal strings with embedded “conversion specifications” which are placeholders for arguments • Conversion specifications are a ‘%’ and a letter with an optional set of arguments in between the ‘%’ and letter. • To print a ‘%’, you need to write ‘%%’ Example: printf(“Here is a number: %d\n”, 10); %d is the conversion specification for signed integers.
Conversion Specifications Converion specifications tell how to translate a data value into a string Conversion Specifications: • %d, %i -- signed integer • %u -- unsigned integer • %f -- floating point number • %c -- character • %s -- string • %x -- hexadecimal value • %p -- pointer Options: • l -- long (32-bit value) • ll -- long long (64-bit value) • n -- field width of n digits • .n -- precision of n digits • 0 -- fill unused field with 0s There are many more! Read man pages, or Google it.
printf quiz! Figure out the output of the following: • printf(“%.3f rounded to 2 decimals is %.2f\n”, 2.325, 2.325); • printf(“%d in hex is: %04x\n”, 24, 24); • printf(“Quizzes are fun, ja?\n”);
scanf scanf(char *format_string, ...); fscanf(FILE*, char *format_string, ...); sscanf(char*, char *format_string, ...); • scanf(....) is fscanf(stdin, ....) • All arguments ot scanf must be pointers (or arrays) • scanf does almost no size checks. It is easy to get a buffer overflow here. Make sure you use a field length specifier with the %s conversion specifer!!! • scanf returns the number of items read.
scanf Examples int items_read; Read a number: int num; items_read = scanf(“%d”, &num); Read a character: char ch; items_read = scanf(“%c”, &ch); Read a string of max length, 79 chars: char buf[80]; buf[79]=‘\0’; // Ensure a terminating NULL. items_read = scanf(“%79s”, buf); Read number after pattern of“a:<num>”: int num; items_read = scanf(“a:%d”, &num); always check the return value of scanf
I/O C++-style C++-style IO is easier for simple stuff Basic classes: • iostream (cout, cin, cerr) • ostringstream, istringstream cout << “Hello World!” << endll; cout << “Boo! “ << 10 << ‘c’ << endl; cerr << “Here’s the error stream” << endl; int n; cin >> n; char ch; cin >> ch;
I/O C++-style continued... ...but harder for complex stuff printf(“%.3f rounded to 2 decimals is %.2f\n”, 2.325, 2.325); …becomes… cout << setprecision(3) << 2.325 << “ rounded to 2 decimals is “ << setprecision(2) << 2.3.25 << endl;
C and C++ I/O compared C-style I/O: • No type safety. What happens with printf(“%d”, ‘c’);? • Conversion specifications have a high learning curve. • Almost all the state of the I/O is contained in the function call. C++ style I/O: • Manipulators are very verbose/annoying • Global state gets changed. When you do “cout << 2.4555”, what precision are you set at? You don’t know. It’s worse with threads. • You get more customizability since C++ I/O is classed based. NEVER mix C and C++ I/O...until you know what ios::sync_with_stdio() does.
Multiple File Development Related functions and variables can be grouped into different files • Each file is considered a “module” • There is no enforced relation between the filename and its contents! main.cc utils.cc int add(int a, int b); int main(void) { int result; printf(“%d\n”, add(a,b)); return 0; } int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; }
The compilation Process Libraries Preprocesser Resolves #define, #include Source code (text) .c .h .c .h Preprocessor Compiler C/C++ code (text) Translates code to Machine Language. It outputs an “object file.” This code is not executable Compiler Object code (bin) Linker Linker Takes object files and resolves references to functions and variables in different files Native Executable (bin) executable
Compilation in Unix The following commands perform the whole compilation process for C and C++: gcc -Wall -g file1.c file2.c -o progname g++ -Wall -g file1.cc file2.cc -o progname You can control which stages get run: • Only run the preprocessor gcc -E file.c • Only run the compiler (don’t link) gcc -Wall -ansi -c file.c • Link these files gcc file1.o file2.o -o progname
Common Compilation options • -Wall enable all warnings • -ansi turn on strict ansi compliance • -v verbose mode on. (lists which directories were searched for include files and libraries) • -g generate debugging symbols • -c compile only (create object file) • -E run the preprocessor only • -s generate assembly code (instead of object code) • -I<dir> prepend directory to include path • -L<dir> prepend directory to library path • -l<libraryname> library to link (looks for lib<libraryname>.so, or .a, or .la) in library path • -o <filename> specifies output filename • -O[n] specifies optimization level (default 0) • -D<name>[=value] define the give preprocessor Tag Always specify -Wall and -g: gcc -Wall -g <filenames...>
Preprocessor and Compiler Preprocessor • Does not understand C/C++ • Produces text that gets read by the compiler Compiler • Expects pure C/C++ (no preprocessor stuff) • Checks syntax • Typechecks your code • Generates, and optimizes, machine code • Produces object files for the linker • Still knows what source file and line numbers
Linker Linker • Resolves dependencies between object files • Places functions, variables, in final location • Generates executables • Does no code optimization • Knows nothing about source files (filenames or line numbers) • Knows nothing about “types” • Matches dependencies by name alone • In C++, linker symbols have been mangled by the compiler (name mangling) to include the return type and argument types. This allows for overloading. • Name mangling is compiler dependent. The linker may not be able to link objects from different compilers.
Object Files • Object files contain compiled code plus an index stating what symbols it has, and what symbols it needs • Use nm (unix) and dumpbin (windows) to list the index • The linker look at all the the object files and tries to find a one-to-one mapping of “needs symbol” to a “has symbol” • .dll and .so files (windows and unix respectively) are object files with special indexes that the OS can understand and use at runtime. • Object File extensions: .o, .obj
Identify who gave the error Error 1: test.c:1:10: #include expects "FILENAME" or <FILENAME> Error 2: /tmp/ccdoe7a7.o: In function `main': /tmp/ccdoe7a7.o(.text+0x7): undefined reference to `foo' collect2: ld returned 1 exit status Error 3: test.c: In function `main': test.c:4: `iii' undeclared (first use in this function) test.c:4: (Each undeclared identifier is reported only once test.c:4: for each function it appears in.) test.c:4: parse error before `989' Error 4: /tmp/ccwltu5R.o: In function `foo': /tmp/ccwltu5R.o(.text+0x0): multiple definition of `foo' /tmp/ccx3bPfI.o(.text+0x0): first defined here collect2: ld returned 1 exit status
Break • Stand up • Stretch • Play with stuff animals • Next topics: • Header Files • Preprocessor
Header Files Common Usages: • Creates an informal module interface (No relation to Java interfaces) • Provides documentation • Each module usually has a header file • Often include the following: • function prototypes • struct and class declarations • typedefs • global variable declarations • Lots of (high-level) comments • Abstracts code for optimization (less common)
point2d.h struct Point2D { int x; int y; }; typedef struct Point2D Point2D; /* returns the result of the vector addition of * a and b. */ Point2D add(Point2D *a, Point2D *b); /* returns the result of the vector subtraction of * b from a */ Point2D sub(Point2D *a, Point2D *b); Header File Example (has error) There is a subtle problem with this header file. It is shown on the next slide.
output.h #include “point2d.h” #include “point3d.h” /* pretty print the different point types to * stdout. */ void Point2D_prettyPrint(Point2D *p); void Point3D_prettyPrint(Point3D *p); We get a compile error. Multiple definition of struct Point2D We use the preprocessor to fix this Header File Problem main.h #include “point2d.h” #include “output.h” int main(void) { Point2D p; p.x = 3; p.y = 5; Point2D_prettyPrint(&p); return 0; }
Preprocessor Overview The preprocessor is a lexical macro engine run before the compiler sees the code • Performs lexical transforms of text • It does not understand C/C++ • There is no concept of types • Transforms are based on lexical substitution • Think Search and Replace • Preprocess directives (commands) start with ‘#’ • Each directive goes on its own line • The # must be the first character on a line. • No semicolons!!!!!!!!!!!! • Newlines end a preprocessor directive, no a ‘;’.
#define • #define Tag Substitution • Replaces all following occurrences of Tag with Substitution • The Substitution may be the empty string • Does not replace Tag if it is inside quotation marks • #define Tag(x,y,z) Substitution • Creates a Function-like macro Tag • The Substitution may refer to the parameters x, y, or z • Only items of the form Tag(x,y,z) will be replaced (Just plain Tag will be ignored) int ar[80]; point->xCoord “lalala”->f03j? #define MAX_SIZE 80 #define GET_FIELD(a,b) a->b int ar[MAX_SIZE]; GET_FIELD(point, xCoord); GET_FIELD(“lalala", f03j?);
The #if, and its variants • #if constant-expression Include the following block of text if constant-expression is non-zero • #ifdef Tag Include the following block if Tag is currently defined (via an earlier #define) • #ifndef Tag Include the following block if Tag is currently not defined (via an earlier #define)
#else, #elif, #endif • #else Used to delimit the else clause in a preprocessor if statement • #elif constant-expression Used to create an else-if clause in a preprocessor if statement • #endif Delimits the end of a preprocessor if statement. There should be exactly one of these for each #if, #ifdef, and #ifndef.
#if examples The DPRINT macro when INCLUDE_DEBUG_PRINT is defined, evaluates to printing code. Otherwise, it evaluates to the empty string. #ifdef INCLUDE_DEBUG_PRINT # define DPRINT(msg) \ fprintf(stderr, “DBG: %s”, msg); #else # define DPRINT(msg) #endif Windows calls the open and close functions, _open, and _close respectively. This is annoying, so let’s fix it with preprocessor macros. #ifdef WIN32 # define open _open # define close _close #endif
#include #include essentially copies and pastes the given file into the current line • There are two forms of #include • They differ in the order in which directories are search for the given file • The search order is implementation defined • Here is the general pattern for different compilers: #include <filename> • Searches the “include path,” then the current directory #include “filename” • Searches the current directory, then the “include path”
#undef, #error, #warning • #undef Tag Removes definition of a Tag. (undoes a #define) • #error message Causes compilation to stop at this line with the given error message. Often this is used with #if blocks to indicate an invalid set of #define macros. • #warning message Causes the compiler to output an warning at this line with the given message. Can be used as strong reminders that a file needs work. • #pragma option Used to pass an option to the compiler. Almost all #pragma commands are compiler-dependent.
#pragma, #line • #pragma option Used to pass an option to the compiler. Almost all #pragma commands are compiler-dependent. • #line number Used to change what line number that the compiler thinks it is on. This is often seen in: • Computer Generated Code (from like flex, bison, or something) • Code outputed by the preprocessor #line to allows programs to make the compiler’s error messages to correspond to the original source file’s line numbers rather than the generated source file’s line numbers.
Useful Macros There are a few macros (#defines) that are given by the compiler that are very useful to know when trying to debug code. • __FILE__ string literal of the current filename • __LINE__ integer literal with the current line number • __func__ string literal of the current function name • __DATE__ string literal of the current date
Other Weird (But Useful) Notes • If you need to break anything into multiple lines, end the line with a single ‘\’. The lexer (thing that reads the file) will concatinate the two lines for you. Avoid doing this unless you have no other choice. • Multiple string literals in a row, with only whitespace separating them, are automatically concatinated. “Hi” “Mom! “ “Here’s a String!” “HiMom! Here’s a String!” • In #define macros, you can get a quoted version of the input argument by prepending the argument with a #. #define mk_str(x) #x used as mk_str(hi) yields “hi” • In #define macros, you can concatinate input arguments with ##. #define foo(a) a##foo used as foo(hi_) yields hi_foo
Complex Example #define DBG_MKSTR(a) #a #define DBG_LOCATION_0(x) __FILE__ "," DBG_MKSTR(x) " -- " #define DBG_LOCATION DBG_LOCATION_0(__LINE__) #define LOCATION_STR(x) DBG_LOCATION x #if !NDEBUG # define LOG(level, msg, args...) \ if (g_debugLevel >= (level)) \ fprintf(stderr, \ #level ": " DBG_LOCATION msg, ##args) #else # define LOG(level, msg, args...) #endif #define INFO 0 #define WARNING 1 #define ERROR 2 Usage: LOG(INFO, “Program got here\n”);
Complex Example Exercise What is the output of the following line assuming NDEBUG is not defined? What is if it is definied? LOG(INFO, “Program got here\n”); Assume the following: • The filename is test.cc • The line number is 123
Preprocessor Summary • The preprocessor does lexical manipulations • Preprocessor commands do not end in semicolons • All preprocessor commands start with a ‘#’ • The most common (important) preprocessor directives are: #include, #define,and #ifndef • Useful directives that are not used enough are: #error, and #warning. • There are a number of provided macros that are very useful to know. In particular, know about __FILE__,__LINE__,__func__
point2d.h struct Point2D { int x; int y; }; typedef struct Point2D Point2D; /* returns the result of the vector addition of * a and b. */ Point2D add(Point2D *a, Point2D *b); /* returns the result of the vector subtraction of * b from a */ Point2D sub(Point2D *a, Point2D *b); (dup) Header File w/ subtle error There is a subtle problem with this header file. It is shown on the next slide.
output.h #include “point2d.h” #include “point3d.h” /* pretty print the different point types to * stdout. */ void Point2D_prettyPrint(Point2D *p); void Point3D_prettyPrint(Point3D *p); We get a compile error. Multiple definition of struct Point2D We use the preprocessor to fix this (dup) Header File Problem main.h #include “point2d.h” #include “output.h” int main(void) { Point2D p; p.x = 3; p.y = 5; Point2D_prettyPrint(&p); return 0; }
Header Guards Header Guards are a common C/C++ idiom Wrap each header file with the lines: #ifndef FILENAME_H #define FILENAME_H <header file body here> #endif /* FILENAME_H */ There is almost no reason to put things outside of header guards
point2d.h #ifndef POINT2D_H #define POINT2D_H struct Point2D { int x; int y; }; typedef struct Point2D Point2D; /* returns the result of the vector addition of * a and b. */ Point2D add(Point2D *a, Point2D *b); /* returns the result of the vector subtraction of * b from a */ Point2D sub(Point2D *a, Point2D *b); #endif /* POINT2D_H */ Header File (corrected)
output.h #ifndef OUTPUT_H #define OUTPUT_H #include “point2d.h” #include “point3d.h” /* pretty print the different point types to * stdout. */ void Point2D_prettyPrint(Point2D *p); void Point3D_prettyPrint(Point3D *p); #endif /* OUTPUT_H */ The header guard for output.h never gets used, but you should still have it just incase. Error fixed since on the second #include of point2d.h, POINT2D_H has been defined and thus, its contents are skipped Header File Problem (fixed) main.h #include “point2d.h” #include “output.h” int main(void) { Point2D p; p.x = 3; p.y = 5; Point2D_prettyPrint(&p); return 0; }
Intermission • Stretch • Go use the restroom • We restart in about 5 minutes • Next topics: • Variables (globals, advanced modifiers) • Classes • Dynamic Memory
Dynamic Memory • Dynamically sized memory in C and C++ must be manually managed • Dynamic memory is not necessarily much slower than static memory • All allocated memory must be freed by the user • Do not free memory twice (double free). • Do not free memory that was allocated by you • Manual memory management allows for finer grained control of your program • It’s not that scary, nor that hard.
new, delete, delete[] • the new operator allocates new memory, initializes it and returns a pointer to it. • the delete operator deallocates memory allocated by new • If you allocate a new array, you must delete it with delete[] and not delete Example: Point2D *p = new Point; delete p; p = NULL; int *ar = new int[50]; delete[] ar; ar = NULL; Improper use of delete and delete[] will cause undefined behavior!