struct, dynamic memory and I/O in C (printf, scanf)
struct and typedef, passing a struct by value and by pointer, the -> operator, arrays of structs, linked lists, malloc/free and the printf/scanf format specifiers. End-of-semester C questions.
The last topic of the semester is the way to keep several pieces of information together in C: struct. A student’s name and grade, the x and y of a point, a node of a list. Besides that, you will also see how a program asks for memory while it runs (malloc) and how it talks to the user (printf, scanf). The question types do not change: “What is printed?” with structs and pointers, printf format questions, memory region questions.
struct
struct point {
int x;
int y;
};
struct point p;
p.x = 7;
p.y = -1;
A struct defines a new type; the variables inside it are called members. Members are accessed with .. In memory the members sit one after another: on the LC-3 a struct point takes two words, p.x in the first word, p.y in the second.
Initialization: struct point p = {3, 4}; (in order x = 3, y = 4).
typedef
To avoid writing struct point every time, a nickname is given:
typedef struct {
char name[12];
int score;
} Student;
Student s = {"Ada", 70};
Structs are copied by value
b = a; copies all the members. Passing a struct to a function is also copying. The function changes the copy, the original does not change. (The exact opposite of arrays: when you pass an array, the address is passed.)
void moveBad(struct point p) { p.x += 10; } /* copy */
void moveGood(struct point *p) { p->x += 10; } /* original */
The -> operator
To access a member through a struct pointer you write (*p).x. Because this is used so often, it has a shorthand: p->x.
struct point p;
p.x = 7;
p.y = -1;
struct point *pp = &p;
pp->y = pp->x * 2;
printf("(%d, %d)\n", p.x, p.y);
(7, 14)
PITFALL ·
*p.x. The precedence of the.operator is higher than*:*p.x=*(p.x), that is “treat the x member as a pointer”. The correct version is(*p).xorp->x.
Arrays of structs
Student s[3] = {{"Ada", 70}, {"Can", 85}, {"Ece", 60}};
s[1].score /* 85 */
s[2].name[0] /* 'E' */
When you increment a struct pointer by one, it advances one struct (pointer arithmetic knows the size of the type).
Linked list
A struct can contain a pointer of its own type. This way the nodes are linked to each other:
struct node {
int val;
struct node *next;
};
head ──► [10 | •]──► [20 | •]──► [30 | NULL]
The traversal pattern:
for (p = head; p != NULL; p = p->next)
... p->val ...
The difference from an array: adding an element requires no shifting, only the pointers change. But you cannot jump directly to the i-th element, you have to walk from the start.
Dynamic memory: malloc and free
If you do not know in advance how much memory the program will ask for while running, you ask for space from the heap.
#include <stdlib.h>
int *v = malloc(4 * sizeof(int)); /* space for 4 ints */
v[0] = 5; /* use it like an array */
free(v); /* give it back when done */
malloc(n)reserves n bytes and returns the starting address (NULL if there is no space).sizeof(T)gives how many bytes a type has; do not write the number by hand.- Memory that is not
freed is lost until the program ends (memory leak).
Building a list:
struct node *head = NULL;
int i;
for (i = 1; i <= 3; i++) {
struct node *n = malloc(sizeof(struct node));
n->val = i * 10;
n->next = head; /* add at the front */
head = n;
}
The list: 30 → 20 → 10. Because each new node is added at the front, the order is reversed.
When you traverse and print:
struct node *p;
for (p = head; p != NULL; p = p->next)
printf("%d ", p->val);
30 20 10
Step by step what happened:
i=1: n1 = {10, NULL} head → n1
i=2: n2 = {20, n1} head → n2 → n1
i=3: n3 = {30, n2} head → n3 → n2 → n1
To delete the whole list there is a pattern as well: while (head) { t = head; head = head->next; free(t); }. If you do free(head) without saving next first, you cannot reach the rest of the list.
The LC-3 memory layout of a struct
Since an int is one word on the LC-3, struct point is two words. If struct point pts[3]; starts at x4000:
x4000 pts[0].x x4001 pts[0].y
x4002 pts[1].x x4003 pts[1].y
x4004 pts[2].x x4005 pts[2].y
The general formula: &pts[i].y = base + 2i + 1. For q->y the compiler adds 1 to the address in q and does an LDR: LDR R1, R0, #1. Because the distance (offset) of the members from the start of the struct is constant, the base+offset mode of LDR/STR is just right for this job.
Memory regions
| Region | What is there? | Lifetime |
|---|---|---|
| Code | the program instructions | the whole program |
| Global data | global and static variables | the whole program |
| Heap | what is obtained with malloc | until freed |
| Stack | local variables, parameters, activation records | until the function returns |
On the LC-3 the stack grows down from high addresses, and the heap grows up from above the global region.
PITFALL · returning the address of a local variable.
int *f(void) { int x = 5; return &x; }is wrong: when f returns, x’s record is removed from the stack and the pointer now points to an invalid place. For data that will outlive the function, use malloc.
printf
int age = 20;
double gpa = 3.456;
char grade = 'B';
printf("Age: %d, GPA: %.2f, Grade: %c\n", age, gpa, grade);
Age: 20, GPA: 3.46, Grade: B
| Specifier | Type | Example | Output |
|---|---|---|---|
%d | int | printf("%d", -7) | -7 |
%c | char | printf("%c", 65) | A |
%f | double | printf("%f", 1.5) | 1.500000 |
%.2f | double, 2 digits | printf("%.2f", 3.14159) | 3.14 |
%s | string | printf("%s", "hi") | hi |
%x / %X | hex | printf("%x", 255) | ff |
%o | octal | printf("%o", 8) | 10 |
%5d | width 5, right | printf("[%5d]", 42) | [ 42] |
%-5d | left-aligned | printf("[%-5d]", 42) | [42 ] |
%05d | pad with zeros | printf("[%05d]", 42) | [00042] |
%% | percent sign | printf("%%") | % |
Escape sequences: \n newline, \t tab, \\ backslash, \" quote.
printf returns the number of characters it wrote: printf("abc\n") returns 4.
scanf
int a, b;
int n = scanf("%d %d", &a, &b);
- The address of the variable is given (
&a). scanf writes what it reads to that address. - When reading a string, no
&is used, because the array name is already an address:scanf("%9s", word);. The width (9) prevents overflow. - The return value is the number of items read successfully. If the user types
12 30, n = 2. %dskips spaces and newlines.
At the LC-3 level, printf and scanf eventually call operating system routines such as TRAP x21 (OUT) and TRAP x20 (GETC). Formatting (turning a number into ASCII digits) is the library’s job; the general form of what you did in week 10 by adding x30.
Common mistakes
| Wrong idea | The truth |
|---|---|
| Passing a struct to a function changes the original | A copy is passed; pass a pointer |
p.x works with a pointer too | With a pointer, p->x |
*p.x = (*p).x | *(p.x); the parentheses are needed |
b = a on structs links the two | An independent copy |
| malloc memory is given back automatically | free is needed |
| Return the address of a local variable | Invalid after the function returns |
scanf("%d", x) | &x |
%5d left-aligns | It right-aligns; use %-5d for left |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed?
struct point { int x; int y; };
/* in main: */
struct point p = {3, 4};
p.x += 2;
printf("%d %d\n", p.x, p.y);Show answer
The initializer list fills the members in order: x = 3, y = 4. p.x += 2 → 5.
What is printed?
void moveBad(struct point p) { p.x += 10; }
void moveGood(struct point *p) { p->x += 10; }
/* in main: */
struct point p = {1, 2};
moveBad(p); printf("%d ", p.x);
moveGood(&p); printf("%d\n", p.x);Show answer
When a struct is passed by value, all its members are copied; moveBad changes the copy, p.x stays 1. moveGood takes the address; p->x means (*p).x and changes the original struct: 11.
What is printed?
struct point a = {5, 6};
struct point b = a;
b.y = 0;
printf("%d %d\n", a.y, b.y);Show answer
Assigning a struct copies all its members. b is an independent copy; changing b.y does not affect a. (Arrays cannot be assigned like this, structs can.)
What is printed?
typedef struct { char name[12]; int score; } Student;
/* in main: */
Student s[3] = {{"Ada", 70}, {"Can", 85}, {"Ece", 60}};
int i, best = 0, sum = 0;
for (i = 0; i < 3; i++) {
sum += s[i].score;
if (s[i].score > s[best].score) best = i;
}
printf("%s %d\n", s[best].name, sum / 3);Show answer
The highest score is 85 (Can, index 1). The sum is 70 + 85 + 60 = 215, 215 / 3 = 71 (integer division).
What is printed?
struct node { int val; struct node *next; };
/* in main: */
struct node c = {30, NULL}, b = {20, &c}, a = {10, &b};
struct node *p;
int s = 0;
for (p = &a; p != NULL; p = p->next)
s += p->val;
printf("%d %d\n", s, a.next->next->val);Show answer
The list: a → b → c → NULL. The loop visits each node: 10 + 20 + 30 = 60. a.next = &b, b.next = &c, c.val = 30.
What is printed?
printf("[%5d][%-5d][%05d]\n", 42, 42, 42);Show answer
%5d: at least 5 characters, right-aligned (3 spaces on its left). %-5d: left-aligned (spaces on its right). %05d: pad with 0 instead of spaces.
What is printed?
printf("%.2f %8.3f|\n", 3.14159, 3.14159);Show answer
%.2f: 2 digits after the point, rounded: 3.14. %8.3f: 3 decimal digits (3.142), total width 8, right-aligned: 3 spaces in front.
What is printed?
printf("%x %X %o %c\n", 255, 255, 8, 65);Show answer
%x is hex in lowercase (255 = 0xff), %X in uppercase, %o octal (8 = 10 in octal), %c an ASCII character (65 = 'A').
The user types 12 30 and presses Enter. What is printed?
int a, b;
int n = scanf("%d %d", &a, &b);
printf("%d %d %d\n", n, a, b);Show answer
scanf returns the number of values it read and assigned successfully: 2. The values are written to the addresses (&a, &b). If the & is forgotten, scanf writes to a random address.
What is printed?
int *v = malloc(4 * sizeof(int));
int i;
for (i = 0; i < 4; i++) v[i] = i * i;
printf("%d %d\n", v[3], v[1] + v[2]);
free(v);Show answer
malloc reserves space for 4 ints from the heap and returns the starting address. v is used like an array: 0, 1, 4, 9. v[3] = 9, v[1] + v[2] = 1 + 4 = 5. The space is given back with free.
What is printed?
struct point pts[2] = {{1, 2}, {3, 4}};
struct point *q = pts;
q++;
printf("%d %d\n", q->x, (*q).y + pts[0].x);Show answer
q++ advances one STRUCT (two ints at once): q = &pts[1]. q->x = 3. (*q).y = 4, + pts[0].x (1) = 5. -> and (*q). are the same thing.
Where in memory do these live: a local variable, a global variable, and a block returned by malloc?
Show answer
Local variables are in the function's activation record, on the stack. Global and static variables are in the global data region. Blocks obtained with malloc are on the heap; they do not disappear when the function returns, they live until they are freed. Returning the address of a local array is an error, returning the address of a malloc'd block is correct.
Verification: All code snippets were compiled and run as C99 with Zig cc (clang); the scanf questions were fed the input in the file verify/SEN2201/c/w14.in; the outputs were copied exactly (verify/SEN2201/c/w14.c and w14b.c, node verify/SEN2201/c/run.mjs w14).