SEN2201 · Week 14 · Programming in C

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.

structmemberdot operator (.)arrow operator (->)typedefarray of structslinked listnodedynamic memory allocationmallocfreeheapprintfscanfformat specifierfield widthprecision

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).x or p->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

RegionWhat is there?Lifetime
Codethe program instructionsthe whole program
Global dataglobal and static variablesthe whole program
Heapwhat is obtained with mallocuntil freed
Stacklocal variables, parameters, activation recordsuntil 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
SpecifierTypeExampleOutput
%dintprintf("%d", -7)-7
%ccharprintf("%c", 65)A
%fdoubleprintf("%f", 1.5)1.500000
%.2fdouble, 2 digitsprintf("%.2f", 3.14159)3.14
%sstringprintf("%s", "hi")hi
%x / %Xhexprintf("%x", 255)ff
%ooctalprintf("%o", 8)10
%5dwidth 5, rightprintf("[%5d]", 42)[ 42]
%-5dleft-alignedprintf("[%-5d]", 42)[42 ]
%05dpad with zerosprintf("[%05d]", 42)[00042]
%%percent signprintf("%%")%

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.
  • %d skips 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 ideaThe truth
Passing a struct to a function changes the originalA copy is passed; pass a pointer
p.x works with a pointer tooWith a pointer, p->x
*p.x = (*p).x*(p.x); the parentheses are needed
b = a on structs links the twoAn independent copy
malloc memory is given back automaticallyfree is needed
Return the address of a local variableInvalid after the function returns
scanf("%d", x)&x
%5d left-alignsIt right-aligns; use %-5d for left

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

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
5 4

The initializer list fills the members in order: x = 3, y = 4. p.x += 2 → 5.

Question 2medium

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
1 11

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.

Question 3easy

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
6 0

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.)

Question 4medium

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
Can 71

The highest score is 85 (Can, index 1). The sum is 70 + 85 + 60 = 215, 215 / 3 = 71 (integer division).

Question 5medium

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
60 30

The list: a → b → c → NULL. The loop visits each node: 10 + 20 + 30 = 60. a.next = &b, b.next = &c, c.val = 30.

Question 6medium

What is printed?

printf("[%5d][%-5d][%05d]\n", 42, 42, 42);
Show answer
[ 42][42 ][00042]

%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.

Question 7medium

What is printed?

printf("%.2f %8.3f|\n", 3.14159, 3.14159);
Show answer
3.14 3.142|

%.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.

Question 8easy

What is printed?

printf("%x %X %o %c\n", 255, 255, 8, 65);
Show answer
ff FF 10 A

%x is hex in lowercase (255 = 0xff), %X in uppercase, %o octal (8 = 10 in octal), %c an ASCII character (65 = 'A').

Question 9medium

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
2 12 30

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.

Question 10medium

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
9 5

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.

Question 11hard

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
3 5

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.

Question 12hard

Where in memory do these live: a local variable, a global variable, and a block returned by malloc?

Show answer
Stack, global data area, heap

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).

Review card

struct point { int x; int y; }; | struct point p = {3, 4}; | p.x typedef struct {...} Student; -> Student s; struct assignment/passing = COPY ALL MEMBERS (by value) | pass a pointer to change it p->x == (*p).x | . on a variable, -> on a pointer array of structs: s[i].score | q++ advances one struct Linked list: struct node { int val; struct node *next; }; for (p = head; p; p = p->next) malloc(n * sizeof(T)) space from the heap, returns the address | free(p) gives it back Memory: local -> stack | global/static -> global region | malloc -> heap printf: %d %c %f %s %x %X %o | %5d right | %-5d left | %05d zero | %.2f | %8.3f scanf("%d", &x) wants an address, returns the number of items read | no & for %s (the array name is an address) printf returns the number of characters it wrote