C++: Memory Management, Summary

Preview:

DESCRIPTION

Christian Schulte cschulte@kth.se Software and Computer Systems School of Information and Communication Technology KTH – Royal Institute of Technology Stockholm, Sweden. C++: Memory Management, Summary. ID1218 Lecture 10 2009-11-30. Overview. Memory management case studies - PowerPoint PPT Presentation

Citation preview

C++: MEMORY MANAGEMENT, SUMMARY

ID1218 Lecture 10 2009-11-30

Christian Schultecschulte@kth.se

Software and Computer SystemsSchool of Information and Communication

TechnologyKTH – Royal Institute of Technology

Stockholm, Sweden

Overview Memory management case studies

resizable arrays freelists reference counting

Summary C and C++ summary… … and questions you should be able to answer

L10, 2009-11-30ID1218, Christian Schulte

2

Case studies

Memory Management

L10, 2009-11-30

3

ID1218, Christian Schulte

Memory Management How to…

fit C++ structure, example: resizable arrays make efficient, example: free lists

Who does what? who allocates? who is responsible for deallocation? example: reference counting

L10, 2009-11-30ID1218, Christian Schulte

4

Resizable Arrays

L10, 2009-11-30

5

ID1218, Christian Schulte

Resizable Arrays Should behave like an array

the number of elements can be changed provides correct construction, deletion,

assignment supports array access operator [] can be passed as constant reference polymorphic with respect to element type can be mixed with "normal" arrays

L10, 2009-11-30ID1218, Christian Schulte

6

Resizable Arrays Members

current size: unsigned int element array: *Element

First attempt for integers make polymorphic in last step

L10, 2009-11-30ID1218, Christian Schulte

7

Resizable Arrays: Classclass IRA {private: unsigned int _n; int* _x;public: IRA(unsigned int n=8); IRA(const IRA& ira); IRA& operator=(const IRA& ira); void resize(unsigned int n); int get(unsigned int i) const; void set(unsigned int i, int x); ~IRA();};

L10, 2009-11-30ID1218, Christian Schulte

8

Constructorclass IRA {private: …public: IRA(unsigned int n=8) : _n(n), _x(new int[_n]) {} …}; Any member function defined inside class is

inline Member functions can also be defined outside

L10, 2009-11-30ID1218, Christian Schulte

9

Inline Definitionclass IRA {private: …public: IRA(unsigned int n=8); …};inlineIRA::IRA(unsigned int n) : _n(n), _x(new int[_n]) {} Any member function defined inside class is inline Member functions can also be defined outside

L10, 2009-11-30ID1218, Christian Schulte

10

Outline Definition Header file:

class IRA {private: …public: IRA(unsigned int n=8);…};

Implementation fileIRA::IRA(unsigned int n) : _n(n), _x(new int[_n]) {}

L10, 2009-11-30ID1218, Christian Schulte

11

Copy ConstructorIRA::IRA(const IRA& ira) : _n(ira._n), _x(new int[_n]) { for (unsigned int i=0; i<_n; i++) _x[i] = ira._x[i];}

Design: nothing shared between copiesL10, 2009-11-30ID1218, Christian Schulte

12

DestructorIRA::~IRA() { delete [] _x;}

Design: nothing shared between copies

L10, 2009-11-30ID1218, Christian Schulte

13

Assignment OperatorIRA&IRA::operator=(const IRA& ira) { if (this != &ira) { delete [] _x; _n = ira._n; _x = new int[_n]; for (unsigned int i=0; i<_n; i++) _x[i] = ira._x[i]; } return *this;}

Design: nothing shared between copies

L10, 2009-11-30ID1218, Christian Schulte

14

Accessinline intIRA::get(unsigned int i) const { return _x[i];}inline voidIRA::set(unsigned int i, int x) { _x[i]=x;}

Must go into header fileL10, 2009-11-30ID1218, Christian Schulte

15

Access: Assert Proper Useinline intIRA::get(unsigned int i) const { assert(i < _n); return _x[i];}inline voidIRA::set(unsigned int i, int x) { …}

Checked at runtime requires #include <cassert> runtime error, if violated (can be looked for in debugger) ignored, if the macro NDEBUG is defined pass –DNDEBUG from commandline

L10, 2009-11-30ID1218, Christian Schulte

16

Resizing the ArrayvoidIRA::resize(unsigned int n) { int* x = new int[n]; unsigned int m = std::min(n,_n); for (unsigned int i=0; i<m; i++) x[i] = _x[i]; delete [] _x; _n = n; _x = x;}

std::min requires #include <algorithm>L10, 2009-11-30ID1218, Christian Schulte

17

Overloading []: First Attemptclass IRA {private: unsigned int _n; int* _x;public: IRA(unsigned int n=8); IRA(const IRA& ira); IRA& operator=(const IRA& ira); void resize(unsigned int n); int operator[](unsigned int i); ~IRA();};

L10, 2009-11-30ID1218, Christian Schulte

18

Overloading []: First Attemptinline int IRA::operator[](unsigned int i) { assert(i < n); return _x[i];} Does not work: ira[7] = 4;

must pass a reference instead Does not work: ira[7] if ira is const IRA&

must also provide const version

L10, 2009-11-30ID1218, Christian Schulte

19

Overloading []: Second Attempt

class IRA {private: unsigned int _n; int* _x;public: IRA(unsigned int n=8); IRA(const IRA& ira); IRA& operator=(const IRA& ira); void resize(unsigned int n); int& operator[](unsigned int i); const int& operator[](unsigned int i) const; ~IRA();};

L10, 2009-11-30ID1218, Christian Schulte

20

Overloading []: Second Attempt

inline int& IRA::operator[](unsigned int i) { assert(i < n); return _x[i];} Works for: ira[7] = 4;

passes a reference to position in array

L10, 2009-11-30ID1218, Christian Schulte

21

Overloading []: Second Attempt

inline const int& IRA::operator[](unsigned int i) const {

assert(i < n); return _x[i];}

Works for ira[7] if ira is const IRA& provides const version

L10, 2009-11-30ID1218, Christian Schulte

22

Generic Resizable Arraystemplate <typename T>class RA {private: unsigned int _n; T* _x;public: RA(unsigned int n=8); RA(const RA<T>& ra); RA<T>& operator=(const RA<T>& ra); void resize(unsigned int n); T& operator[](unsigned int i); const T& operator[](unsigned int i) const; ~RA();};

L10, 2009-11-30ID1218, Christian Schulte

23

Generic Copy Constructortemplate <typename T>RA<T>::RA(const RA<T>& ra) : _n(ra._n), _x(new T[_n]) { for (unsigned int i=0; i<_n; i++) _x[i] = ra._x[i];} Define in header file What is executed:

default constructor? yes! copy constructor? no! assignment operator? yes!

L10, 2009-11-30ID1218, Christian Schulte

24

Mixing With Normal ArraysRA<int> x(42);void f(int* a);void g(int a[]);f(x); g(x);

Both calls do not work: x is of type RA<int>

Simple solution: f(&x[0]); g(&x[0]);

L10, 2009-11-30ID1218, Christian Schulte

25

Mixing With Normal Arraystemplate <typename T>class RA { …public: … operator T*(void) { return _x; }}; Dangerous…

RA<int> x(42); int* y = x;x.resize(1024);y[0] = 4;

L10, 2009-11-30ID1218, Christian Schulte

26

Freelists

L10, 2009-11-30

27

ID1218, Christian Schulte

Efficient Lists Common behavior

create and delete list cells very often Memory allocation and deallocation is

expensive Idea

store allocated but unused list cells in a freelist allocate from freelist whenever possible deallocate to freelist

L10, 2009-11-30ID1218, Christian Schulte

28

Integer Listsclass IL {private: int _hd; IL* _tl;public: IL(int h, IL* t = NULL); int hd() const; void hd(int h); IL* tl() const; void tl(IL* t);}; Basic required functionality

no default constructor, no … Naïve memory policy: delete single list cell

refine, if different policy needed

L10, 2009-11-30ID1218, Christian Schulte

29

Integer ListsinlineIL::IL(int h, IL* t) : _hd(h), _tl(t) {}inline intIL::hd() const { return _hd; }inline voidIL::hd(int h) { _hd=h; }inline IL*IL::tl() const { return _tl; }inline voidIL::tl(IL* t) { _tl=t; }

L10, 2009-11-30ID1218, Christian Schulte

30

Overloading Memory Allocation#include <cstdlib>class IL {private: int _hd; IL* _tl;public: IL(int h, IL* t = NULL); int hd() const; void hd(int h); IL* tl() const; void tl(IL* t); static void* operator new(size_t s); static void operator delete(void* p);};

L10, 2009-11-30ID1218, Christian Schulte

31

Overloading Memory Allocationinline void*IL::operator new(size_t s) { return malloc(s);}inline voidIL::operator delete(void* p) { free(p);}

Same behavior as beforeL10, 2009-11-30ID1218, Christian Schulte

32

Freelist as Static Member#include <cstdlib>class IL {private: int _hd; IL* _tl; static IL* _free;public: IL(int h, IL* t = NULL); int hd() const; void hd(int h); IL* tl() const; void tl(IL* t); static void* operator new(size_t s); static void operator delete(void* p); static void flush();};

L10, 2009-11-30ID1218, Christian Schulte

33

Implementation File: BrokenIL* IL::_free = NULL;voidIL::flush() { for (IL* f=_free; f!=NULL; f=f->_tl) free(f); _free = NULL;} Freelist is initially empty Access after free!

L10, 2009-11-30ID1218, Christian Schulte

34

Implementation File: BetterIL* IL::_free = NULL;voidIL::flush() { IL* f=_free; while (f!=NULL) { IL* t = f->_tl; free(f); f = t; } _free = NULL;}

L10, 2009-11-30ID1218, Christian Schulte

35

Allocating from the Freelistinline void*IL::operator new(size_t s) { if (_free == NULL) { return malloc(s); } else { IL* il = _free; _free = _free->_tl; return il; }}

Same behavior as beforeL10, 2009-11-30ID1218, Christian Schulte

36

Deallocating to the Freelistinline voidIL::operator delete(void* p) { IL* il = static_cast<IL*>(p); il->_tl = _free; _free = il;}

L10, 2009-11-30ID1218, Christian Schulte

37

Generic Freelists Not straightforward

no static members in parametric classes Solution

implement type independent freelist manager pass freelist manager as extra argument to

new and delete (placement new and delete) read a book…

L10, 2009-11-30ID1218, Christian Schulte

38

Reference Counting

L10, 2009-11-30

39

ID1218, Christian Schulte

Sharing Arrays Desired functionality

resizable array without copies (use a lot of memory) same array

What memory management policy unshared arrays: whoever created array is

responsible (directly or indirectly) to delete shared array: when is the array not any longer

used?

L10, 2009-11-30ID1218, Christian Schulte

40

Reference Counting Separate

array proper: store elements handle: implement allocation protocol

provide access to array proper Store in array proper: reference count

…how many handles refer to array array creation through handle: just one when array becomes shared through additional

handle: increment when handle is deleted: decrement if reference count hits zero: delete array

L10, 2009-11-30ID1218, Christian Schulte

41

Array Object Properclass AO {public: unsigned int n; int* x; unsigned int rc;};

L10, 2009-11-30ID1218, Christian Schulte

42

Array Handleclass AH {private: AO* _ao;public: AH(unsigned int n=8); AH(const AH& ah); AH& operator=(const AH& ah); ~AH(); int& operator[](unsigned int i); …};

L10, 2009-11-30ID1218, Christian Schulte

43

Array Handle ConstructorAH::AH(unsigned int n) : _ao(new AO) { _ao->n = n; _ao->x = new int[n]; _ao->rc = 1;}

L10, 2009-11-30ID1218, Christian Schulte

44

Array Handle: Object Accessinline int&AH::operator[](unsigned int i) { return _ao->x[i];}…

L10, 2009-11-30ID1218, Christian Schulte

45

Array Handle DestructorinlineAH::~AH() { _ao->rc--; if (_ao->rc == 0) { delete [] _ao->x; delete _ao; }} Only if no more handles refer to object, delete object Different design: provide constructor and destructor

for array objectL10, 2009-11-30ID1218, Christian Schulte

46

Copy ConstructorinlineAH::AH(const AH& ah) : _ao(ah._ao) { _ao->rc++;}

Array object is used by one additional handle

L10, 2009-11-30ID1218, Christian Schulte

47

Assignment OperatorinlineAH::operator=(const AH& ah) { if (this != &ah) { if (--_ao->rc == 0) { delete _ao->x; delete _ao; } _ao = ah._ao; _ao.rc++; } return *this;} Unsubscribe handle from current object

possibly delete object Subscribe to new object

L10, 2009-11-30ID1218, Christian Schulte

48

Putting the Object at its Place…class AH {private: class AO { public: unsigned int n; int* x; unsigned int rc; }; AO* _ao;public: AH(unsigned int n=8); …}; Object should never be used without controlling handle

L10, 2009-11-30ID1218, Christian Schulte

49

Smart Pointers Generic wrapper class overloading

operators -> and * Part of C++ standard library Use reference counting inside

L10, 2009-11-30ID1218, Christian Schulte

50

Problems With Reference Counting

Cost every assignment and copy is expensive all access has to go through one additional

indirection Cyclic datastructures

suppose reference counted lists create cyclic list all reference counts in cycle will be at least one never deleted

L10, 2009-11-30ID1218, Christian Schulte

51

Summary: Basics

L10, 2009-11-30ID1218, Christian Schulte

52

L10, 2009-11-30ID1218, Christian Schulte

Integer Types Basic integer type int

no guarantee on range in C++ today, often 32 bits, anytime soon 64 bits, … depends on machine (think of embedded…)

Can be augmented with short or long int never shorter than short int (at least 16 bits) int never longer than long int (at least 32 bits)

Can be modified with signed (default) or unsigned

short signed int -32768 … 32767 short unsigned int 0 … 65535 just for example!

53

L10, 2009-11-30ID1218, Christian Schulte

Floating Point Types Single precision float

often: 32 bits Double precision double

often: 64 bits

54

L10, 2009-11-30ID1218, Christian Schulte

Character Type Character type char

no guarantee on size, often 8 bits (ASCII, not Unicode!)

unspecified whether signed or unsigned

Character constants in single quotes 'a', '1' escape sequences: '\n' for newline, etc

Also available: wchar_t for wide characters

55

L10, 2009-11-30ID1218, Christian Schulte

Boolean Type Boolean type bool

constants true and false

Rather late addition! watch out for int with zero as false and any

other value true!

56

L10, 2009-11-30ID1218, Christian Schulte

Constants Variables taking an immutable value

cannot be changed abbreviation for commonly used values

Use const modifierconst double pi = 3.1415;

assignment to pi not possible

57

L10, 2009-11-30ID1218, Christian Schulte

Operators and Expressions No left to right order

readInt() – readInt()with input 2 and 3 can be either 1 or -1

Integer division not guaranteed to round towards zero (book uses down)

8/-5can be either -1 or -2

Comma operator: a,b evaluates to b weird things possible!

58

L10, 2009-11-30ID1218, Christian Schulte

Unitialized Variables Variables not by guarantee initialized

unless global or static

Always give initial value important for objects

59

L10, 2009-11-30ID1218, Christian Schulte

Static Casts Cast at least one into double

static_cast<double>(x)/y;

Other casts dynamic_cast casting objects, later const_cast later reinterpret_cast just do it as long size

fits… C-style cast ((double) x)

dangerous, combines everything, don't use!

60

L10, 2009-11-30ID1218, Christian Schulte

Example Questions What is the relation between

sizeof(int) sizeof(short int) sizeof(long int)

Answersizeof(short int)

sizeof(int)sizeof(int) sizeof(long int)

61

Summary: Arrays and Pointers

L10, 2009-11-30ID1218, Christian Schulte

62

L10, 2009-11-30ID1218, Christian Schulte

C-style Arrays C-style arrays

int a[42];creates an array of 42 integers

access cout << a[1]; assignment a[1] = a[2]+a[3]; ranges from a[0] to a[41]

Dimension of array must be constant can be evaluated at compile time to constant

(eg 2*4) illegalint a[n] where n is variable!

63

L10, 2009-11-30ID1218, Christian Schulte

Arrays are Pointers C-Style arrays are basically pointers

point to beginning of array Array access a[i] translates to *(a+i) Common idiom: pointer arithmetic

pointer +/- integer: offset to pointer pointer – pointer: distance between pointers

64

L10, 2009-11-30ID1218, Christian Schulte

Creating Dynamic Arrays An array of size n

int* a = new int[n];

Release memory laterdelete [] a;

never forget the []: important for arrays of objects (calling destructor)

65

L10, 2009-11-30ID1218, Christian Schulte

Example Questions Is this legal?

int n = 6;int a[n];

const int n = 5;int b[n*4];

Answer no yes

66

L10, 2009-11-30ID1218, Christian Schulte

Example Questions Value of x and y…

int a[] = {4,6,8}; int b[] = {1,3,5};int x = *(a+5-*(b+1));*(b+a[1]-a[0]) = *a; int y = *a+*(b+(&b[2]-&b[0]));

value of x: *(a+5-*(b+1)) = a[5-b[1]] = a[5-3] = a[2] = 8

assignment: b[a[1]-a[0]] = a[0], that is b[2]=a[0]

value of y: a[0] + b[b+2-b] = a[0] + b[2] = 4 + 4 = 8

67

Summary: Functions

L10, 2009-11-30ID1218, Christian Schulte

68

L10, 2009-11-30ID1218, Christian Schulte

Functions: Issues Overloading Declarations Definitions Default parameters Inline functions Call-by-value and call-by-reference

69

L10, 2009-11-30ID1218, Christian Schulte

Example Questions Is this legal?

int f(int);double f(double x) { return x+1.0; } yes, absolutely!

70

L10, 2009-11-30ID1218, Christian Schulte

Example Questions What is the value of z?

int f(double x) { return static_cast<int>(x)-1; }int f(int* x) { return *(x+1); }int f(int x, int y=4) { return x+y-1; }int a[] = {1,2,3};int z = a[f(1.0)]-f(1)+f(7,5)+f(&a[1]); f(1.0) = 0 f(1) = 1+4-1 = 4 f(7,5) = 7+5-1 = 11 f(&a[1]) = *(a+2) = 3

71

Summary: Objects and Classes

L10, 2009-11-30ID1218, Christian Schulte

72

L10, 2009-11-30ID1218, Christian Schulte

Accessors vs Mutators Fundamental difference

change object state: mutator do not change state: accessor

Accessors need to be declared const required for passing by const reference

73

L10, 2009-11-30ID1218, Christian Schulte

Initializer Lists In constructor after parameter list

after colon: comma separated list in order of declaration of members

Only initialize, avoid initialization with subsequent assignment

74

L10, 2009-11-30ID1218, Christian Schulte

Copying and Assignment Copying is controlled by copy constructorIntCell(const IntCell& c) : x(c.x) {}

Assignment is controlled by assignment operatorIntCell& operator=(const IntCell& c) { if (this != &c) x=c.x; return *this;}

These are synthesized by compiler if missing required for resource management

75

L10, 2009-11-30ID1218, Christian Schulte

Destructor~IntCell() { delete x;}

When object is deleted by delete (for heap allocated) by going out of scope (for automatically allocated)

destructor is invoked for resource management

76

L10, 2009-11-30ID1218, Christian Schulte

Default Constructor A constructor with no arguments (or all

arguments with default values) automatically generated, if no constructors

provided

Important for initializationIntCell c;

invokes the default constructor

77

L10, 2009-11-30ID1218, Christian Schulte

Example Find three mistakes and correct

class A {private: int* x;public: A() : x(new int[1]) {} ~A() { delete x; } int val() { return *x; } A(const A& a) : x(new int[1]) { *x = a.val(); }

A& operator=(const A& a) { delete [] x; x = new int[1]; *x=a.val(); return *this; }};

78

L10, 2009-11-30ID1218, Christian Schulte

Example Find three mistakes and correct

class A {private: int* x;public: A() : x(new int[1]) {} ~A() { delete x; } int val() { return *x; } A(const A& a) : x(new int[1]) { *x = a.val(); }

A& operator=(const A& a) { delete [] x; x = new int[1]; *x=a.val(); return *this; }};

const missing!

79

L10, 2009-11-30ID1218, Christian Schulte

Example Find three mistakes and correct

class A {private: int* x;public: A() : x(new int[1]) {} ~A() { delete x; } int val() { return *x; } A(const A& a) : x(new int[1]) { *x = a.val(); }

A& operator=(const A& a) { delete [] x; x = new int[1]; *x=a.val(); return *this; }};

must be: delete [] x

80

L10, 2009-11-30ID1218, Christian Schulte

Example Find three mistakes and correct

class A {private: int* x;public: A() : x(new int[1]) {} ~A() { delete x; } int val() { return *x; } A(const A& a) : x(new int[1]) { *x = a.val(); }

A& operator=(const A& a) { delete [] x; x = new int[1]; *x=a.val(); return *this; }};

test for self-assignment missing

81

L10, 2009-11-30ID1218, Christian Schulte

Example What is the value of z?

class A {public: int x; A() : x(7) {} A(int y) : x(y+4) {} A(const A& a) : x(a.x-1) {} A& operator=(const A& a) { x = a.x+3; return *this; }};A a(4); A b; A c(a); b = c; int z = b.x; a.x = 8, c.x = 7, b.x = 10

82

Summary: Fine Points for Objects

L10, 2009-11-30ID1218, Christian Schulte

83

L10, 2009-11-30ID1218, Christian Schulte

Static Members Static member is part of class, not of

instance maintain data that is shared among all

instances of a class Requires declaration and definition Declaration

use keyword static done in header

Definition use class name done in implementation

84

L10, 2009-11-30ID1218, Christian Schulte

Namespaces Organize multiple functions, classes, etc

togethernamespace N {… }

Inside the namespace, as alwaysC

Outside namespaceN::C

Convenience: make available single class using N::C; namespace using namespace N;

Reference to C in global namespace::C

85

Summary: Inheritance

L10, 2009-11-30ID1218, Christian Schulte

86

L10, 2009-11-30ID1218, Christian Schulte

Static versus Dynamic Dispatch Static dispatch: use compile-time type of

reference to object default behavior in C++

Dynamic dispatch: use runtime type for finding member function

default behavior in Java in C++: mark member functions as virtual important case: virtual destructor

87

L10, 2009-11-30ID1218, Christian Schulte

Defaults with Inheritance Copy constructor

invokes copy constructor on base classes invokes copy constructor on newly added data

members Assignment operator

invokes assignment operator on base classes invokes assignment operator on newly added data

members Destructor

invokes destructors on newly added data members invokes destructors on base classes

88

L10, 2009-11-30ID1218, Christian Schulte

Abstract Methods and Classes Member functions can be declared

abstract in base class declare as virtual declaration followed by = 0;

Must be implemented in inheriting classes

instance creation only for-non abstract classes possible

89

L10, 2009-11-30ID1218, Christian Schulte

Type Conversions Assume we know that p is a student,

reallyPerson* p = new

Student(123,"Eva",2); Use dynamic castStudent* s = dynamic_cast<Student*>(p);

performs check and cast at runtime returns NULL, if not a Student

90

L10, 2009-11-30ID1218, Christian Schulte

Example What is the value of z?

class A { public: virtual int x() { return 1; } int y() { return -1; } };class B : public A { public: virtual int x() { return 6; } };class C : public B { public: int y() { return 3; } };class D : public B { public: virtual int x() { return B::x()+8; } }; C* c = new C(); D* d = new D();int z = c->y()+dynamic_cast<A*>(d)->x()+dynamic_cast<A*>(c)->y();

dynamic_cast<A*>(d)->x() use dynamic dispatch and returns 6+8=14

dynamic_cast<A*>(c)->y() uses static dispatch and returns -1

91

Summary: C Programming

L10, 2009-11-30

92

ID1218, Christian Schulte

L10, 2009-11-30ID1218, Christian Schulte

Major Differences… No classes

use structs (see later) No references

use pointers No overloading No templates No default parameters All local variable declarations must be at

beginning of function Memory management Libraries

93

L10, 2009-11-30ID1218, Christian Schulte

The Struct Type Combine data into one structure

struct IntPair {int x; int y;

}; Also available in C++, corresponds to

class IntPair {public:

int x; int y;};

94

L10, 2009-11-30ID1218, Christian Schulte

Preprocessor Macros Conditional compilation Textual substitution

#define FOOVALUE 1 Macros

#define max(x,y) (x>y?x:y) unsafe! unsafe! things go wrong

b = max(a--,c--);

95

L10, 2009-11-30ID1218, Christian Schulte

C-Style Memory Management Allocate n bytes from heap

void* malloc(size_t n); Free memory block p obtained by malloc

void free(void* p); Never mix new+delete with malloc+free

never use delete on malloc block and vice versa

96

Outlook

L10, 2009-11-30

97

ID1218, Christian Schulte

Outlook Discussion

functional programming concurrent programming object-oriented programming imperative programming system programming

Course summary

L10, 2009-11-30ID1218, Christian Schulte

98

Recommended