The C++ Programming Language – Part 8

This post will be mostly for personal reference as I go through Bjarne Stroustrop’s “The C++ Programming Language” 4th edition textbook. Some of the notes will appear random.

Chapter 7 – Pointers, Arrays, and References (continued)

Run-time range checking on array bounds is neither guaranteed nor common. Be careful with loops dealing with arrays. You can’t dynamically size an array on a function stack in C++. For example if int n is passed to a function, int v[n] is not allowed. You can use the passed-in n though with C++ containers, like vectors. vector<int> v(n); is legal. Stroustrup recommends that “arrays should be primarily used inside the implementation of a higher level, better behaved data structure.” If you’ve allocated an array on the heap, make sure you deallocate it. Never deallocate an array that has been allocated on the stack. Its a better programming style to use a resource handle rather than a raw array pointer. This way, when the resource handle goes out of scope, its resources will be freed without you having to remember, which is the case for arr in the code below.

#include <iostream>
#include <vector>

using namespace std;

void f(int n)
{
    vector<int> arr(n);
}

int main()
{
    /* Array on the stack */
    int v[10];

    /* Arrays on the heap */
    int *vz = new int[40];
    int *vs = (int *) malloc(20);

    /* Create a vector using a function */
    /* Vector uses arrays in its implementation */
    f(10);

    /* Make sure to deallocate the heap objects with the proper functions:
     *     malloc and free
     *     new and delete
     *     new[] and delete[]
     * Failing to use the matching alloc/dealloc routines can lead to 
     * memory leaks. Try removing the [] in the delete below, valgrind will
     * report "==30548== Mismatched free() / delete / delete []"
     */
    delete[](vz);
    free(vs);

    return 0;
}

An update to the statements above re: dynamically sized stack arrays: A dangerous C++ programmer could consider using the alloca() function to dynamically allocate an array on the stack. It is machine and compiler dependent and is not part of the C++ standard. On many systems its implementation is buggy. Its use is discouraged. I have seen CTF pwnable challenges that use alloca() and allow for stack overflow; just don’t use it.

A C-string is a null terminated array of char. Its usually assumed that char* or const char* are null terminated. Forgetting to null terminate a char array can lead to undefined behavior. Some possibly unexpected facts about arrays: there is no built-in copy, you cannot initialize one array with another, and there is no array assignment:

#include <iostream>

using namespace std;

int main()
{   
    /* legal, normal list initialization */
    int v5[] = {1, 2, 3, 4};
    
    int v6[4] = v5;

    v6 = v5;

    return 0;
}

Output:

array_illegal.cpp: In function ‘int main()’:
array_illegal.cpp:11:17: error: array must be initialized with a brace-enclosed initializer
int v6[4] = v5;
^
array_illegal.cpp:14:8: error: invalid array assignment
v6 = v5;

String literals are statically allocated so they are safe to be returned from functions. The statement return “range error”; is safe since “range error” exists in a read-only data section of the binary and will always be a static pointer. Lets confirm this:

#include <iostream>

using namespace std;

const char* test_literal()
{   
    return "TEST_STRING_LITERAL";
}

int main()
{   
    cout << test_literal() << endl;
    return 0;
}

Compiling this with debug symbols and setting a breakpoint at test_literal(), we can see where the const literal is stored:

   0x40084a <test_literal()+4>     mov    eax, 0x400955
 ► 0x40084f <test_literal()+9>     pop    rbp
   0x400850 <test_literal()+10>    ret   
 
...

pwndbg> x/1s 0x400955
0x400955:	"TEST_STRING_LIT"...

...

pwndbg> maint info sections
Exec file:
    `/home/user/cpp/part_2/chapter_7/string_literal', file type elf64-x86-64.
[15]     0x00400950->0x00400969 at 0x00000950: .rodata ALLOC LOAD READONLY DATA HAS_CONTENTS

A constexpr means “evaluate at compile time” while const means “do not modify in this scope.” Use const to protect things that shouldn’t be touched.  Objects declared as const must be initialized. A very important note: Prefixing the declaration of a pointer with const makes the object, but not the pointer, a constant. To make the pointer itself const, you must use *const. For example:

#include <iostream>

using namespace std;

int main()
{
    char stuff[] = "test";

    /* pointer to a const char */
    const char *ptr1 = stuff;

    /* const pointer to a char */
    char *const ptr2 = stuff;

    /* proof that ptr2's char is mutable */
    ptr2[3] = 'H';

    cout << ptr2 << endl;

    return 0;
}

Output:

user@ubuntu:~/cpp/part_2/chapter_7$ ./const_example 
tesH