Announcements
- Today's Schedule
- Project 1 scores: Low 78, High 99, Mean 92.1
- Project 4 due Wednesday
(discuss buffered input)
- Exam #1 topics
- Complete discussion on dynamic memory allocation
- User-defined types
- The comma operator
- Large program development - sharing state among source files
Exam #1 Topics
- First hourly exam is Friday, in class
- Exam is closed book, closed notes, except for a 1 page 'cheat sheet' which you will also hand in
- Covers all readings listed on the schedule including today, plus
everything we've done in class or homeworks/projects
- Expect mixture of short answers, T/F and multiple choice, and short
programming exercises
- I've posted a file in coursesite with sample exam questions
- Major topics include:
- Using UNIX
- C Language and Programming
- Pointers and Storage Management
Finish Friday's discussion
- We had been discussing dynamic memory allocation
- Mentioned a simple routine to expand the size of a dynamically
allocated array of integers (posted online
here)
- Realized need for pointers to pointers
- We have a few more issues to cover w.r.t. pointers...
Functions that return pointers
- When using routines that return pointers, you must determine who is
responsible for the memory
- Possibilities include:
- Pointer is to a global value -- memory never needs to be free()d, but
might get overwritten by later function calls (e.g., some networking
code)
- Pointer is to a dynamically-allocated local structure that must be
destroyed/freed with another library call
- Pointer is to a dynamically-allocated block of memory that your code
must later free (e.g., strdup())
- Pointer is to a portion of some other block of memory that could later
move or be freed independently of this pointer
(e.g., strstr())
- In general, you need to know if
- You are now responsible to free() this memory
- This memory might get overwritten in the future
Generally, either you need to free() or this is memory that you have no
control over...
Stack/Heap
- Finally, it is sometimes useful to think about exactly where
all this memory really is in your program.
- Let's consider the typical memory model for UNIX processes.
- The stack contains each calling frame of each function invocation, which are popped when the function returns.
- The heap contains dynamically allocated memory.
- The stack and heap grow toward each other.
Why can't malloc'd data be on the stack?
User-Defined Structures
- We have previously mentioned structs, as in
struct complex {
double real;
double imag;
};
which defined a new type
struct complex.
We could also have declared some new variables of that type, in two
ways:
struct complex {
double real;
double imag;
} var1, var2;
struct complex var3, var4;
Access to Structs
- We also saw that we could access contents using the period operator:
var1.real = var2.real + var3.real;
Pointers to structs work as expected:
struct complex *p1, *p2;
p1 = &var1;
printf("%lf\n", (*p1).real);
Parentheses are needed. Could instead use -> operator:
printf("%lf\n", p1->real);
which is an equivalent (and common) shortcut.
User-defined types
- A struct is a user-defined type
- Sometimes we have a need/desire to define new type names
- for convenience
- to make the code more self-documenting
- to make it possible to change the actual base type used for a lot of
variables without rewriting the declarations of all those variables
Typedef
- The typedef operator permits us to use an alternate name
for a defined type. Thus,
typedef char *StringPtr;
typedef struct complex Complex;
StringPtr string;
Complex c1, c2;
would be equivalent to
char *string;
struct complex c1, c2;
Large Program Development
- When you are facing the development of a large program, you need to organize how you will proceed. You might
- Focus on top-down development: write a simple main() which calls other (initially abstract) functions, and then implement them with calls to more functions. During this process, implement the functions as stubs, so that the whole thing can be compiled and some simple testing performed.
- Focus on bottom-up development: identify some key low-level needs (e.g., reading/writing data) and build functions to provide such services.
Ideally, at the same time, also write testing code to exercise your functions and verify their correctness.
- Use a divide-and-conquer strategy, especially when coding in groups. Might just share object files with each other (along with documentation on how to use your code).
- Typically you might use a combination of the above.
Multiple Source Files
- In all of the above approaches, you'll want or need to use multiple source files
- Already know how to compile them...
- Easy enough when you just want to call functions in other files
- Need more if you want to have shared state across multiple files
Sharing State
- A global variable isn't really global -- it's only accessible by those functions in the same source file!
- In order for another source file to be able to access a global declared elsewhere, it must declare that variable to be external:
extern int i;
which essentially says this is a variable with the given type but is actually declared elsewhere (to be resolved at link time).
So any other file in a project can access this variable if they know
the name, and declare it with extern.
Try it!
Not Sharing State
- Preventing access is possible
- If the keyword static is added to the global declaration
static int i;
then no other file can access this variable, even when using extern.
- The same approach can be used for functions (to prevent them from
being called by functions in other source files).
- Enforces modularity---other functions can only access your variables
by using exposed interfaces (externally-accessible functions or truly
global variables).
Try it!