490 likes | 523 Vues
Structures. C and Data Structures Baojian Hua bjhua@ustc.edu.cn. Why need “ Structure ” ?. So far, we ’ d discussed two kinds of data: Simple: char, int, long, double, etc. Scalar: array, pointer, etc. It ’ s inconvenient in some applications: See next slides for an example. Example.
E N D
Structures C and Data Structures Baojian Hua bjhua@ustc.edu.cn
Why need “Structure”? • So far, we’d discussed two kinds of data: • Simple: char, int, long, double, etc. • Scalar: array, pointer, etc. • It’s inconvenient in some applications: • See next slides for an example
Example // We want to represent time as year/month/date: int f () { int year1, year2, month, date; year1 = 2050; year2 = 2020; month = 12; date = 30; date++; // Should we increase year1 or year2? } // The problem is that there is no logical // connection between them. We need “structure”!
Structure Declaration // Start with key word “struct” and followed by // an optional structure tag, and then a list // (one or more) of fields. // Example, to represent a two-dimensional point: struct point2d { int x; int y; }; // note the semicolon // point2d contains two fields x and y, both of // type int. // struct point2d (as a whole) is a (user-defined) // new type.
Variable Definition // Given the declaration above, we may declare a // variable pt: struct point2d pt; // to be of type “struct point2d”, just the same // way as we write: int i;
Structure Fields // Given the variable definition struct point2d pt; // fetching its fields x or y: pt.x; pt.y; // which act as ordinary variables, such as: pt.x = 9; pt.y = 10; // or: printf (“%d\n”, pt.x + pt.y);
x x x y y y Structure in Structure // Having known that structures are types, we may // study its memory layout. Technically, a // structure occupies a piece of continuous space: // So, we may even nest structures // in other structures, as in: struct rect { struct point2d pt1; struct point2d pt2; };
Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x=??? y=??? Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x=??? y=??? Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x==3 y==4 Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x==3 y==4 Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x==3 x==3 y==4 y==4 Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); ax==3 ay==4 3 4
x==3 y==4 Structures and Functions // A function creating a point2d structure: struct point2d create (int ax, int ay) { struct point2d pt; pt.x = ax; pt.y = ay; return pt; } // And a sample call: struct point2d p = create (3, 4); p 3 4
x==3 y==4 Structures as Functions Arguments // Like the structure return value, passing // structures to functions copy the whole // structure field-by-field (call-by-value): struct point2d mult2 (struct point2d pt) { pt.x *= 2; pt.y *= 2; return pt; } // A sample call: struct point2d p = mult2 (initPt); // Much like the previous example // Analysis leave to you
Moral • Structures returned from function and passed to functions are call-by-value • Pros: a simple style of functional programming • result in elegant and easy-to-reason code • ideas from functional languages (lisp, ML, F#), but may also be useful in imperative and OO languages • Cons: may be too inefficient • stupidly copy a big value • semantics inconsistent with array • Next, we’d discuss a more imperative style • Update in place
x==3 y==4 Pointers to Structures // Pointers to structures are just like pointers // to any other kind of data: struct point2d *pt; // declares pt to be a pointer to a structure // ponit2d, which looks like: // To reference x and y, we use: (*pt).x; (*pt).y; // or a more concise shorthand: pt->x; pt->y; pt
Structure Pointers as Functions Arguments // Address passing: void mult2 (struct point2d *pt) { pt->x *= 2; pt->y *= 2; return; } // A sample call (no return value): struct point2d p = creat (3, 4); mult2 (&initPt); // Analysis leave to you
data data data next next next Self-referential Structures // With structures pointer mechanism, we may // write self-referential structures (structures // fields point to same type of structures): struct node { int data; struct node *next; };
Union • A union may hold (at different times) objects of different types and sizes • compilers take care of the space allocation, alignment, etc. • Unions provide a way to manipulate different kinds of data in a single area of storage
i, a[0] a[1] Union // A sample union type: union intOrArray { int i; int a[2]; }; // which declares intOrArray to have two fields: // integer i and int array a of length 2.
i, a[0] a[1] Union // A sample union type: union intOrArray { int i; int a[2]; }; // which declares intOrArray to have two fields: // integer i and int array a of length 2. union intOrArray u; u.a[0] = 88; u.a[1] = 99; // u.i == ???
i, a[0] a[1] Union // A sample union type: union intOrArray { int i; int a[2]; }; // which declares intOrArray to have two fields: // integer i and int array a of length 2. union intOrArray u; u.i = 77;; // u.a[2] = ???
i, a[0] a[1] Union // A sample union type: union intOrArray { int i; int a[2]; }; // Even worse. What if we write an “output”? void output (union intOrArray x) { printf (“%d\n”, x.i); // or: printf (“%d, %d\n”, x.a[0], x.a[1]); }
Moral on Union • Union gives us a magical bag to let us bypass the C’s type system • store an integer, but take out an array • It’s the programmers’ responsibility to keep union data consistent • But what if the union value is written by others or from a library, which we may know nothing about?
tag i, a[0] a[1] Tagged Union // In order to distinguish union states, we // annotate unions with tags: struct ss { enum {INT, ARRAY} tag; union intOrArray { int i; int a[2]; } u; };
tag i, a[0] a[1] Tagged Union // And for variable temp: struct ss temp; // now we must take care of temp’s tag fields: temp.u.i = 99; temp.tag = INT; // Or to store an array: temp.u.a[0] = 3; temp.u.a[1] = 4; temp.tag = ARRAY;
tag i, a[0] a[1] Tagged Union // data accessing is guarded: void output (struct ss x) { switch (x.tag) { case INT: printf (“%d”, x.u.i); break; case ARRAY: printf (“%d, %d”, x.u.a[0], x.u.a[1]); break; default: // error handling… } }
typedef---Define Our Own Types • And it’s rather stupid and annoying to always write long type names like these: struct point2d pt; struct point2d *pp; • And even some types are rather crazy: int (*f[10])(int, int); int (*f(char c))(int, int); • Are there some better methods? • Yes! It’s the “typedef”
typedef---Define Our Own Types // C has a mechanism called “typedef” allowing us // to define our own types (abbreviations). // For instance: typedef struct point2d pt2d; // defines “pt2d” to be a type equivalent to // “struct point2d”. And next, “pt2d” could be // used just as any other type: pt2d pt; pt.x = 3; pt.y = 4;
typedef---Define Our Own Types // Not only structures can be typedefed, but also // any type name, even the pre-defined ones in C: typedef int sizeT; sizeT i; i = sizeof (int); sizeT j; j = i;
typedef---Define Our Own Types // More examples of typedef: typedef int *t1; typedef int (*t2)(); typedef char **t3; typedef int *t4[10]; typedef int (*t5)[10]; typedef int *t6(); typedef char (*(*t7())[])(); typedef (*(*t8[3])())[5];
typedef---Define Our Own Types // More examples of typedef: typedef int *t1; typedef int (*t2)(); typedef char **t3; typedef int *t4[10]; typedef int (*t5)[10]; typedef int *t6(); typedef char (*(*t7())[])(); typedef char (*(*t8[3])())[5]; // How to read these crazy stuffs? // Next, to show the general principal, I’ll take // type t7 as a running example:
Inorder Analysis typedef char (*(*t7())[])(); // t7 is a function, takes void // function returns a pointer // pointing to an array // array storing pointers // pointing to functions (taking void returning // char) // Really annoying! Better solutions? Yes!
Preorder typedef char (*(*t7())[])(); // Step by step: typedef char (*t71)(); typedef t71 t72[]; typedef t72 *t7(); // Question: How to rewrite t8?
Type Cast Revisit • We’d discussed type cast • Ex: (int)3.14 • unsafe in general • Type cast on pointers is more flexible, subtle, and dangerous • we’d discuss an example below
Type Cast on Pointers char c = ‘a’; char *p = &c; int *q = (int *)p; *q = 9999; short *r = (short *)q; *r = 88; ‘a’ 1000 ? 1001 p ? 1002 ? 1003 ? 1004
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 Example int sum (int x, int y) { int temp = x + y; return temp; } int main () { char *s = (char *)sum; return 0; }
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 Example int sum (int x, int y) { int temp = x + y; return temp; } int main () { char *s = (char *)sum; char a[100]; return 0; }
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 Example int sum (int x, int y) { int temp = x + y; return temp; } int main () { char *s = (char *)sum; char a[100]; int i; for (i=0; i<100; i++) a[i] = s[i]; return 0; }
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 Example int main () { char *s = (char *)sum; char a[100]; int i; for (i=0; i<100; i++) a[i] = s[i]; // call array a? return 0; } int sum (int x, int y) { int temp = x + y; return temp; }
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 Example int main () { char *s = (char *)sum; char a[100]; int i; for (i=0; i<100; i++) a[i] = s[i]; // call array a? a(3, 4); return 0; } int sum (int x, int y) { int temp = x + y; return temp; }
1000 s sum: temp=x+y 1004 return temp 1008 ? 1012 ? 1016 An Array Can be Called! typedef int (*tf)(int, int); int main () { char *s = (char *)sum; char a[100]; int i; for (i=0; i<100; i++) a[i] = s[i]; // call array a? (tf(a)) (3, 4); return 0; } int sum (int x, int y) { int temp = x + y; return temp; }
Summary of Typedefs • typedef doesn’t create any new type name • it creates shorthands for known types • typedef is an important mechanism: • to make program maintenance and porting easy • to enable information hiding • more on this later
Bit-fields // What’s bit-fields, and why do we want them? // We start by defining a student structure: struct student { char *name; int gender; // 0 or 1 int classNum; // 0~7 int isLeader; // 0 or 1 }; // See the problem?
Bit-fields // To save space, We use bit-fields: struct student { char *name; unsigned int gender : 1; // 0 or 1 unsigned int classNum : 3; // 0~7 unsignedint isLeader : 1; // 0 or 1 }; // Question: what the size of “struct student”?
Bit-fields // Sample operations: #define FEMALE 0x00 #define MALE 0x01 #define CLASS1 0x00 … #define CLASS8 0x07 struct student s; s.name = “Bill Gates”; s.gender = MALE; s.classNum = CLASS0; s.isLeader = 0x00;
Bit-fields for Data // Besides saving space, some externally-imposed // data formats require compact data rep’. // As a final example in this slide, we discuss // x86’s interrupt descriptor (id):
Bit-fields for Data struct idtEntry { unsigned int offset0_15 : 16; unsigned int selector : 16; unsigned int notUsed : 5; unsigned int zeros : 3; unsigned int reserved : 5; unsigned int dpl : 2; unsigned int p : 1; unsigned int offset16_31 : 16; };