Laboratory 12

Laboratory 12 โ€“ Loading Text Files into Dynamic Arrays

This laboratory connects file processing with dynamic memory. You will count lines in a text file, load all lines into a dynamically allocated array of strings, save this array back to a file, and build a small reusable text-file library.

text files dynamic arrays char ** fgets malloc libtext.a

Laboratory Overview

What is new in this laboratory.

Laboratory 11 introduced basic file operations and command-line arguments. In this laboratory, you will move from reading a file character by character to treating a text file as a dynamic array of lines.

Main idea of this lab:
file โ†’ count lines โ†’ allocate char ** โ†’ load lines โ†’ process lines โ†’ save lines

This is the direct preparation for sorting text files in Laboratory 13. Once the file is represented as an array of strings, you can reuse the sorting knowledge from Laboratories 9 and 10.

Learning Outcomes

  • count lines in a text file and use the result for allocation,
  • allocate a dynamic array of string pointers,
  • allocate memory for each line separately,
  • load a text file into memory line by line,
  • save an array of strings to a text file,
  • free a dynamically allocated array of strings correctly,
  • design text.h and text.c,
  • build a reusable static library libtext.a,
  • prepare reusable code for sorting files in Laboratory 13.

How this laboratory continues Lab 11

The previous lab read files; this lab loads them into memory in a reusable form.

Laboratory 11 Laboratory 12
open a file open, count, load and save a file
read characters read complete lines
print or count immediately store lines in dynamic memory
single-purpose programs reusable text-file library
File numbering convention: Laboratory 11 ended with ex42 as an optional extra task. This laboratory continues the main sequence with ex43โ€“ex47.

Task 0 โ€” Reminder and preparation

Shown. Log in to the AGH server and prepare the working directory.

Step 1 โ€” Log in to the AGH UNIX server

ssh your_login@student.agh.edu.pl

Step 2 โ€” Go to the course directory

cd ~/I2PL

Step 3 โ€” Create the directory for Laboratory 12

mkdir lab12
cd lab12

Step 4 โ€” Prepare a sample file

cat > countries.txt << EOF
Poland
Germany
Japan
Italy
United Kingdom
United States of America
Portugal
Ukraine
EOF
In this laboratory, every successful allocation must have a matching free. If allocation fails in the middle of loading a file, release everything that was already allocated.

Task 1 โ€” Count lines as a reusable function

Implement yourself. Move line counting from a program into a function.

Step 1 โ€” Create the file

nano ex43_count_lines_function.c

Step 2 โ€” Function declaration

int f_lines(const char *name);

Step 3 โ€” Expected behaviour

  • Open the file with the given name.
  • Count text lines.
  • Count the last non-empty line even if it does not end with \n.
  • Return the number of lines.
  • Return -1 if the file cannot be opened.
Hint: two useful variables

Use lines to count newline characters and last to remember the last character read. If the file is not empty and last is not '\n' or '\r', add one more line.

Task 2 โ€” Load all lines into a dynamic array

Implement yourself. Read a text file into char **.

Step 1 โ€” Create the file

nano ex44_load_lines.c

Step 2 โ€” Function declaration

char **f_load(const char *name, int *size);

Step 3 โ€” Expected behaviour

  • Count the number of lines using f_lines.
  • Allocate an array of char *.
  • Read each line from the file.
  • Allocate exactly enough memory for every line.
  • Copy the line into its own allocated block.
  • Store the number of loaded lines in *size.
  • Return the array, or NULL on error.

Step 4 โ€” Suggested reading buffer

char buffer[1024];

while (fgets(buffer, sizeof(buffer), in) != NULL) {
    /* allocate memory for this line */
}
The buffer is temporary. Do not store buffer directly in the array. Allocate memory and copy its content.
Hint: allocation for one line
tab[i] = malloc(strlen(buffer) + 1);

if (tab[i] == NULL) {
    /* release previously allocated lines */
}

Task 3 โ€” Save an array of lines to a file

Implement yourself. Write loaded lines into another file.

Step 1 โ€” Create the file

nano ex45_save_lines.c

Step 2 โ€” Function declaration

int f_save(const char *name, int size, char *tab[]);

Step 3 โ€” Expected behaviour

  • Open output file for writing.
  • Write each line from tab.
  • Close the file.
  • Return the number of successfully written lines.
  • Return -1 if the file cannot be opened.

Step 4 โ€” Basic writing pattern

for (int i = 0; i < size; i++) {
    fputs(tab[i], out);
}
f_save writes exactly the strings stored in the array. If the lines were loaded with fgets, the newline character is usually already part of each string. In that case, do not add an extra '\n' when saving the file.

Task 4 โ€” Free an array of lines

Complete. Write one helper function that releases all memory allocated by f_load.

Step 1 โ€” Function declaration

void f_free(int size, char *tab[]);

Step 2 โ€” Function skeleton

void f_free(int size, char *tab[]) {
    if (tab == NULL) {
        return;
    }

    /* free each line */

    /* free the array itself */
}
Hint

First release tab[i] for every valid line, then release tab.

Task 5 โ€” Build text.h, text.c and libtext.a

Implement yourself. Move the reusable functions into a small text-file library.

Step 1 โ€” Create the header file

nano text.h

Step 2 โ€” Header file content

#ifndef TEXT_H
#define TEXT_H

int f_lines(const char *name);
char **f_load(const char *name, int *size);
int f_save(const char *name, int size, char *tab[]);
void f_free(int size, char *tab[]);

#endif

Step 3 โ€” Create implementation file

nano text.c

Move implementations of f_lines, f_load, f_save and f_free into this file.

Step 4 โ€” Compile and build the library

gcc -Wall -c text.c
ar rcs libtext.a text.o
This library will be reused directly in Laboratory 13 when sorting text files line by line.

Task 6 โ€” Test the text library

Implement yourself. Create a program that loads a file and saves it under another name.

Step 1 โ€” Create the test program

nano ex46_test_text_library.c

Step 2 โ€” Required usage

./ex46_test_text_library input.txt output.txt

Invalid usage should display:

Usage: ex46_test_text_library input output

Step 3 โ€” Test workflow

int size = 0;
char **lines = f_load(argv[1], &size);

if (lines == NULL) {
    return 1;
}

f_save(argv[2], size, lines);
f_free(size, lines);

Step 4 โ€” Compile with the library

gcc -Wall ex46_test_text_library.c -L. -ltext -o ex46_test_text_library

Task 7 โ€” Display loaded lines with line numbers

Implement yourself. Create a small program that proves the file is really stored as an array of strings.

Step 1 โ€” Create the file

nano ex47_numbered_lines.c

Step 2 โ€” Required usage

./ex47_numbered_lines file.txt

Step 3 โ€” Output example

1: Poland
2: Germany
3: Japan
This program should use f_load and f_free. Do not duplicate file-loading code here.

Test Questions โ€” Review before the next class

Try to answer these questions before opening the answers.

1. Why do we count lines before loading the file?

We need to know how many pointers to allocate in the dynamic array of strings.

2. Why can't we store the address of a local buffer in the array?

The buffer is reused for every line and exists only in the current function scope. Each loaded line must have its own allocated memory.

3. What does char ** represent in this laboratory?

It represents a dynamic array of pointers to strings. Each element points to one loaded line.

4. Why do we need f_free?

The file is loaded into multiple allocated memory blocks: one block for the array of pointers and one block for each line. All of them must be released.

5. Why is this library useful for sorting files?

Sorting needs random access to lines, and an array of strings gives exactly that. Once the file is loaded into char **, sorting lines becomes similar to sorting strings.

Checklist before submission

  • ex43_count_lines_function.c implements f_lines,
  • ex44_load_lines.c loads a file into char **,
  • ex45_save_lines.c saves an array of strings into a file,
  • f_free releases all memory allocated by f_load,
  • text.h contains include guards and declarations,
  • text.c contains definitions of reusable functions,
  • libtext.a has been created,
  • ex46_test_text_library.c tests load/save/free workflow,
  • ex47_numbered_lines.c displays loaded lines with line numbers,
  • all opened files are closed,
  • all allocated memory is released correctly.