1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

char* make_asterisks_string(int num_asterisks) {
    char* asterisks = malloc(sizeof(*asterisks) * (num_asterisks + 1));
    // Declare 'asterisks' as type char* (address of a char) and initialize it to the address of a
    // newly allocated buffer on the HEAP segment where we will store the asterisks and the
    // null terminator ('\0').
    //
    // ∙ malloc(sizeof(*asterisks) * (num_asterisks + 1)) allocates a buffer on the HEAP segment
    //   sufficient to store 'num_asterisks' asterisks and the null terminator.
    //
    // ∙ sizeof(*asterisks) is the number of bytes needed to store ONE object of the same
    //   type as (*asterisks).
    //   ∘ asterisks is a char* because it was declared that way.
    //   ∘ *asterisks is a char because adding a '*' to an expression removes an '*' from its type.
    //   ∘ sizeof(*asterisks) is the same as sizeof(char), but we always use sizeof(«expression»),
    //     not sizeof(«type»).
    //   ∘ sizeof(*asterisks) is the number of bytes needed to store a char on our platform.
    //   ∘ sizeof(*asterisks) * (num_asterisks + 1) is the number of bytes needed to store
    //     (num_asterisks + 1) char's on our platform.  In other words, it is the number of bytes
    //     needed to store the whole string of asterisks, plus the null terminator ('\0').
    //
    // ∙ "Buffer" just means an area of memory.  Most of the time, we can think of it as an array.
    //   We use the word "buffer" because there will be times later when we allocate space on the
    //   heap for something other than an array, such as an int or a struct object (covered soon).
    //
    //
    // PITFALLS
    //
    // ∙ DO NOT FORGET THE ASTERISK!!!
    //   ∘ GOOD:  ▒▒▒ = malloc(sizeof(*asterisk) * ▒▒▒)
    //                                ▲
    //                                └─IMPORTANT!!!
    //   ∘ BAD!:  ▒▒▒ = malloc(sizeof( asterisk) * ▒▒▒)
    //                                ▲
    //                                └─Missing - will cause problems
    //
    // ∙ Always use sizeof(«EXPRESSION»), -NOT- sizeof(«TYPE»)
    //   ∘ GOOD:  ▒▒▒ = malloc(sizeof(*asterisk) * ▒▒▒)
    //   ∘ BAD!:  ▒▒▒ = malloc(sizeof(char) * ▒▒▒)
    //   
    // ∙ Do not add a typecast      // ex: char* asterisks = (char*) malloc(…).
    //   ∘ GOOD:  ▒▒▒ = malloc(sizeof(*asterisk) * ▒▒▒)
    //   ∘ BAD:  ▒▒▒ = (char*)malloc(sizeof(*asterisk) * ▒▒▒)
    //                  └──┬──┘
    //                    BAD!

    //_________________________________________________
    // At this point, 'address' contains the address of our newly allocated buffer (area in memory).
    // However, that buffer has not been initialized.  Until we write some bytes (characters in this
    // case), we cannot predict what will be in that memory.
    //
    // Fill the buffer at address 'asterisks' with the asterisks, plus the null terminator ('\0').
    // In programming lingo, this is sometimes called "populating the array".
    for(int i = 0; i < num_asterisks; i++) {
        asterisks[i] = '*';
    }

    asterisks[num_asterisks] = '\0';  // REMEMBER!!!!  Add the null terminator.
    // If we didn't write the '\0' after the last asterisk, then when we printed the string of
    // asterisks, we *might* get some additional junk after the asterisks.  We can't predict what
    // will be in memory until it has been initialized (assigned a value for the first time).

    // IMPORTANT: The buffer on the heap segment will still be allocated (i.e., usable by us) even
    // after the function returns.

    return asterisks;
    // Return the address of the newly allocated buffer on the heap.
}

int main(int argc, char* argv[]) {

    char* asterisks = make_asterisks_string(7);
    // 'asterisks' now contains the address of a buffer containing the string "*******" including
    // the null terminator.

    printf("asterisks == \"%s\"\n", asterisks);
    // Output:
    // asterisks == "*******"
    
    // IMPORTANT:  For every call to malloc(…), we must call free(…) with the address that malloc(…)
    // returned.  Otherwise, you will have a memory leak.  (Memory leaks will be covered next week.)
    free(asterisks);
    
    return EXIT_SUCCESS;
}
/* vim: set tabstop=4 shiftwidth=4 fileencoding=utf-8 noexpandtab: */

© Copyright 2023 Alexander J. Quinn         This content is protected and may not be shared, uploaded, or distributed.