munotes®

Structures

Chapter Forty-Two

Syllabus topic 4, "User-defined data types- structure and union"

Pages 205 to 210 of 222

In one line

A structure groups values of different types into one object with named members, so that things that belong together can be handled as one thing.

Why it exists

An array holds many values of one type. A book has a title, an author, a subject and an identifier: four values of three types that belong to one book. Without structures you would keep four parallel arrays and hope their indexes stayed in step.

char title[50][100];      /* the title of book i   */
char author[50][100];     /* the author of book i  */
int  id[50];              /* the id of book i      */

Nothing in that code says the three are connected. Sort one and you have destroyed the other two. A structure says it in the type.

Declaring one

struct book {
    char title[60];
    char author[40];
    char subject[40];
    int id;
};

That declares a type, struct book, and creates no object. The semicolon after the closing brace is required and is forgotten constantly.

Then objects of it:

struct book b1;
struct book b2 = {"Programming with C", "Kernighan and Ritchie",
                  "Computer Science", 101};
struct book b3 = {.id = 102, .title = "Let Us C"};   /* C99, by name */

struct book is the type's full name, including the keyword. That is why typedef is so common with structures, and chapter 13 promised this:

typedef struct book Book;
Book b1;                    /* now one word */

or in one declaration:

typedef struct {
    char title[60];
    int id;
} Book;

Reaching the members

Two operators, and the second waits for a pointer.

OperatorUsed onExample
.a structureb1.id
->a pointer to a structurep->id

p->id means (p).id. The brackets in that form are required, because . binds tighter than , so p.id would mean (p.id).

The practical: books

MU's Practical 9. She asks for Title, Author, Subject and Book ID, and the details of two printed.

#include <stdio.h>
#include <string.h>

struct book {
    char title[60];
    char author[40];
    char subject[40];
    int id;
};

void print_book(const struct book *b)
{
    printf("  Book ID : %d\n", b->id);
    printf("  Title   : %s\n", b->title);
    printf("  Author  : %s\n", b->author);
    printf("  Subject : %s\n", b->subject);
}

int main(void)
{
    struct book first = {"The C Programming Language",
                         "Kernighan and Ritchie",
                         "Computer Science", 101};
    struct book second;

    /* filling one member at a time, which is what scanf into a struct looks like */
    second.id = 102;
    strcpy(second.title, "Programming with C");
    strcpy(second.author, "E. Balagurusamy");
    strcpy(second.subject, "Computer Science");

    printf("First book:\n");
    print_book(&first);
    printf("\nSecond book:\n");
    print_book(&second);

    printf("\nreaching a member directly: first.title is \"%s\"\n", first.title);
    printf("through a pointer: (&first)->id is %d\n", (&first)->id);
    return 0;
}
First book:
  Book ID : 101
  Title   : The C Programming Language
  Author  : Kernighan and Ritchie
  Subject : Computer Science

Second book:
  Book ID : 102
  Title   : Programming with C
  Author  : E. Balagurusamy
  Subject : Computer Science

reaching a member directly: first.title is "The C Programming Language"
through a pointer: (&first)->id is 101
munotes.in205

Structures

print_book takes const struct book *. A pointer rather than the structure itself, because a structure is copied when passed by value and this one is 144 bytes; const because the function only reads. Chapter 40's advice, applied.

A structure is copied

This is where a structure differs from an array, and it is worth a program of its own.

#include <stdio.h>

struct point { int x; int y; };

void by_value(struct point p)
{
    p.x = 999;
    printf("   inside by_value, p.x is %d\n", p.x);
}

void by_pointer(struct point *p)
{
    p->x = 999;
    printf("   inside by_pointer, p->x is %d\n", p->x);
}

struct point moved(struct point p, int dx, int dy)
{
    p.x += dx;                       /* changes the copy */
    p.y += dy;
    return p;                        /* and returns it */
}

int main(void)
{
    struct point a = {1, 2};
    struct point b;

    b = a;                           /* a whole-structure copy, in one line */
    b.y = 50;
    printf("after b = a and b.y = 50: a is (%d, %d), b is (%d, %d)\n",
           a.x, a.y, b.x, b.y);

    printf("before by_value  : a.x is %d\n", a.x);
    by_value(a);
    printf("after  by_value  : a.x is %d   <- unchanged\n", a.x);

    printf("before by_pointer: a.x is %d\n", a.x);
    by_pointer(&a);
    printf("after  by_pointer: a.x is %d   <- changed\n", a.x);

    struct point c = moved(a, 10, 20);
    printf("moved(a, 10, 20) gave (%d, %d), and a is still (%d, %d)\n",
           c.x, c.y, a.x, a.y);
    return 0;
}
after b = a and b.y = 50: a is (1, 2), b is (1, 50)
before by_value  : a.x is 1
   inside by_value, p.x is 999
after  by_value  : a.x is 1   <- unchanged
before by_pointer: a.x is 1
   inside by_pointer, p->x is 999
after  by_pointer: a.x is 999   <- changed
moved(a, 10, 20) gave (1009, 22), and a is still (999, 2)

Three facts out of that one program:

  1. b = a copies every member, including arrays inside the structure. An array cannot be assigned; a structure containing one can.
  2. Passing by value copies, so by_value could not change the caller's structure.
  3. A structure can be returned by value, which is the second answer to chapter 33's "how do I return two values".

Two structures still cannot be compared with ==. Compare member by member, or use memcmp and understand the padding problem below.

An array of structures

This is the shape of almost every real program, and it is what chapter 44 is built on.

munotes.in206

Structures

#include <stdio.h>

#define STUDENTS 4

struct student {
    int roll;
    char name[20];
    int marks[3];
    double average;
};

int main(void)
{
    struct student class[STUDENTS] = {
        {101, "Anita Desai",   {63, 71, 58}, 0.0},
        {102, "Rahul Mehta",   {48, 52, 61}, 0.0},
        {103, "Fatima Shaikh", {88, 79, 91}, 0.0},
        {104, "Priya Nair",    {35, 42, 40}, 0.0}
    };

    for (int i = 0; i < STUDENTS; i++) {
        int total = 0;
        for (int j = 0; j < 3; j++) {
            total += class[i].marks[j];
        }
        class[i].average = (double) total / 3;
    }

    printf("%-6s %-16s %5s %5s %5s %9s %s\n",
           "Roll", "Name", "P1", "P2", "P3", "Average", "Result");
    for (int i = 0; i < STUDENTS; i++) {
        printf("%-6d %-16s %5d %5d %5d %9.2f %s\n",
               class[i].roll, class[i].name,
               class[i].marks[0], class[i].marks[1], class[i].marks[2],
               class[i].average, class[i].average >= 40 ? "pass" : "fail");
    }

    int best = 0;
    for (int i = 1; i < STUDENTS; i++) {
        if (class[i].average > class[best].average) {
            best = i;
        }
    }
    printf("\nhighest average: %s with %.2f\n",
           class[best].name, class[best].average);
    return 0;
}
Roll   Name                P1    P2    P3   Average Result
101    Anita Desai         63    71    58     64.00 pass
102    Rahul Mehta         48    52    61     53.67 pass
103    Fatima Shaikh       88    79    91     86.00 pass
104    Priya Nair          35    42    40     39.00 fail

highest average: Fatima Shaikh with 86.00

class[i].marks[j] is a member of a structure in an array, and that member is itself an array. Read it left to right: element i of class, its marks member, element j of that. Nesting like this is normal and needs no new rules.

The "find the best" loop is chapter 36's largest-element pattern with class[i].average in place of a[i], and it starts at index 0 for the same reason.

Nesting structures

A member may itself be a structure.

#include <stdio.h>

struct date {
    int day;
    int month;
    int year;
};

struct employee {
    int id;
    char name[30];
    struct date joined;
    double salary;
};

int main(void)
{
    struct employee e = {7001, "Joseph D'Souza", {15, 6, 2021}, 48500.0};

    printf("%s (id %d)\n", e.name, e.id);
    printf("joined on %02d/%02d/%d\n",
           e.joined.day, e.joined.month, e.joined.year);
    printf("salary %.2f\n", e.salary);

    e.joined.year = 2022;
    printf("after correction, the year is %d\n", e.joined.year);
    return 0;
}
Joseph D'Souza (id 7001)
joined on 15/06/2021
salary 48500.00
after correction, the year is 2022

e.joined.year is read left to right and the nesting can go as deep as you like.

sizeof a structure, and padding

sizeof a structure is at least the sum of its members and is usually more. The compiler inserts unused bytes, called padding, so that each member starts at an address the processor likes.

#include <stdio.h>

struct badly_ordered {
    char  a;        /* 1 byte  */
    int   b;        /* 4 bytes */
    char  c;        /* 1 byte  */
};

struct well_ordered {
    int   b;        /* 4 bytes */
    char  a;        /* 1 byte  */
    char  c;        /* 1 byte  */
};

int main(void)
{
    printf("sizeof(char) %zu + sizeof(int) %zu + sizeof(char) %zu = %zu\n",
           sizeof(char), sizeof(int), sizeof(char),
           sizeof(char) + sizeof(int) + sizeof(char));
    printf("sizeof(struct badly_ordered) is %zu\n", sizeof(struct badly_ordered));
    printf("sizeof(struct well_ordered)  is %zu\n", sizeof(struct well_ordered));
    printf("same members, different order, different size\n");
    return 0;
}
munotes.in207

Structures

sizeof(char) 1 + sizeof(int) 4 + sizeof(char) 1 = 6
sizeof(struct badly_ordered) is 12
sizeof(struct well_ordered)  is 8
same members, different order, different size

The exact numbers depend on the machine and the compiler; what is guaranteed is that the total may exceed the sum. Two consequences:

  1. Never assume a structure's size. Use sizeof.
  2. Never compare two structures with memcmp. The padding bytes hold whatever they held, so two structures with identical members can compare unequal. Compare member by member.

What this does NOT mean

A structure declaration does not create an object. struct book { ... }; declares a type. struct book b; creates one.

The semicolon after the closing brace is not optional.

struct book is not shortened to book automatically. In C the keyword is part of the name. A typedef is what shortens it. (C++ differs, which is why many books get this wrong.)

p->x is not different from (*p).x. It is the same thing with better syntax.

Two structures cannot be compared with ==. Compare members.

sizeof a structure is not the sum of its members. Padding.

A structure is not passed by reference. It is copied, unlike an array. Pass a pointer when it is large or when the function must change it.

Quick revision

  • struct name { members }; declares a type; the trailing semicolon is required.
  • struct name object; creates one. typedef shortens the type name.
  • . on a structure, -> on a pointer to one. p->m is (*p).m.
  • Initialise with a braced list, or by member name with C99 designators.
  • A structure is copied on assignment, on being passed and on being returned.
  • That copying is why b = a works for a structure containing an array, though an array cannot be assigned.
  • Pass a pointer, and const if read-only, when the structure is large or must be changed.
  • Cannot be compared with ==; compare member by member, never with memcmp.
  • sizeof a structure is at least the sum of its members, because of padding.
  • Structures nest, and an array of structures is the ordinary way to hold records.

Test yourself

1. What is the difference between a structure declaration and a structure variable?

The declaration introduces a type and allocates nothing. A variable of that type is an object and occupies memory.

munotes.in208

Structures

2. How do you reach member id through a pointer p to a structure?

p->id, which is the same as (*p).id.

3. Can you assign one structure to another?

Yes. b = a copies every member, including any arrays inside it.

4. Can you compare two structures with ==?

No. Compare member by member. memcmp is not a substitute, because padding bytes may differ.

5. Why is sizeof(struct { char a; int b; char c; }) usually 12 rather than 6?

Because the compiler inserts padding so that each member is suitably aligned, and pads the whole structure so that an array of them stays aligned.

6. Write a structure for a book with a title, an author and an id, and create one.

struct book { char title[60]; char author[40]; int id; };
struct book b = {"Programming with C", "Balagurusamy", 101};

7. Why pass a large structure by pointer rather than by value?

Because passing by value copies every byte of it at every call. A pointer copies eight bytes, and const on it keeps the promise that the function will not change the original.

What can be asked on this, and how to answer it

"What is a structure? Explain its declaration, initialisation and member access with an example." Define it as a user-defined type grouping members of possibly different types, give the declaration with its trailing semicolon, the braced initialiser, and both . and ->. Mention the typedef habit and say why it exists.

"Write a program to store and display the details of a book using a structure." Give this chapter's Practical 9 program, with the four members MU names and two books printed through a function taking const struct book *.

"Distinguish between a structure and an array." An array holds many elements of one type reached by an index; a structure holds members of possibly different types reached by name. An array cannot be assigned or returned, and a structure can. An array argument decays to a pointer, while a structure argument is copied.

"Explain nested structures with an example." A member of a structure may itself be a structure. Give struct employee containing struct date joined, and show e.joined.year.

"What is structure padding? Why does it happen?" Unused bytes the compiler inserts between members, and after the last one, so that each member begins at an address its type requires and so that an array of the structure stays aligned. It means sizeof a structure may exceed the sum of its members, so a size must never be assumed and two structures must not be compared with memcmp.

munotes.in209

Structures

"How is a structure passed to a function?" By value, which copies it, or by pointer, which does not. Give both, and say that a pointer with const is the usual choice for anything larger than a few members.

munotes.in210

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself for the same subject.

Report or request
Done!