memo: C++ | Pointer Usages

Table of contents

Pointer

(2023-11-09) Review:

A pointer variable and a regular variable are 2 ways of indexing memory.

  • A pointer variable stores the address of the start byte of a variable. And its type indicates how many bytes the complete data takes.

  • Pointer variable is an address, a unsigned integer, whose size depends on system architecture (32-/64-bit addr). While a variable represents the whole data.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    // "pointer.c"
    #include <stdio.h>
    int main(){
        int a = 1;
        int* p = &a;
        printf("a = %d \n", a);
        printf("p = %p \n", p);
        printf("address of a = %p \n", &a);
        printf("#bytes of a = %ld \n", sizeof(a));
        printf("#bytes of p = %ld \n", sizeof(p));
    
        int b[2][2] = {1,2,3,4}, *ptr_b = &b[2][2];
        printf("#bytes of b = %ld \n", sizeof(b));
        printf("#bytes of ptr_b = %ld \n", sizeof(ptr_b));
    }
    

    Build gcc pointer.c -o pointer and run ./pointer.

    Output:
    1
    2
    3
    4
    5
    6
    7
    
    a = 1 
    p = 0x7ffe45d7c88c 
    address of a = 0x7ffe45d7c88c 
    #bytes of a = 4 
    #bytes of p = 8 
    #bytes of b = 16 
    #bytes of ptr_b = 8
    
  • The *p (dereferenced p) and i are equivalent at all time.

    1
    2
    3
    
    int i = 1;
    int* p = &i;
    bool cf_res = (*p == i);   // 1
    
  • (2023-11-11) char* indicates the direction: from pointer * to char. Similarly, char** means from a pointer * to another pointer *, then to char.

    POINTERS in C++ - YouTube - The Cherno

  • void* ptr = 0. Address 0 is NULL or nullptr. void means dismissing the type of the data it points to.


Reference

Note: C doesn’t have reference. SO

A reference variable is an alias. Compared to pointer, it’s an already dereferenced address.

1
2
3
4
5
int i = 10

int* ptr = &i;  // an address

int& ref = i;   // an alias
  1. Declaration and initialization must be performed at the same time.

    1
    2
    3
    4
    5
    
    int& ref;   // incorrect
    ref = i;
    
    int* ptr;
    ptr = &i;   // ok
    
  2. Reference variable cannot be reassigned.

    1
    2
    3
    4
    5
    6
    
    int i =0, j =1;
    int& ref1 = i;
    int& ref1 = j; // error: redeclaration of ‘int& ref1’
    
    int& ref2 = ref1; // ok
    std::cout << ref2 << std::endl;  // 0
    
  3. Reference shares the same memory as the variable, not occupying another memory.

    • Pass reference into function to modify the source data directly. Pointer needs deferencing to access the data.

    • Python only has reference without pointer.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    #include<iostream>
    int main(){
        int i = 0;
        std::cout << &i << std::endl;    // 0x7ffd18889e74
    
        int* ptr = &i;
        std::cout << &ptr << std::endl;  // 0x7ffd18889e78
    
        int& ref = i;
        std::cout << &ref << std::endl;  // 0x7ffd18889e74
    }
    
  4. Use reference for function parameters and return types.

    Use references when you can, and pointers when you have to. C++ FAQ

    1. Iteration:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      
      #include <iostream>
      #include <vector>
      
      int main(){
          vector<int> myVec;
          myVec = {1,2,3,4,5};
      
          // for (int i:myVec) // will copy item to i
          for (int& i : myVec) // use reference to avoid copy data
              std::cout << i << std::endl;
      }
      
    2. Pass reference to function to avoid copying:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      void Function(const std::vector<int>& myVec){
          for (int i : myVec)
              std::cout << i <<std::endl;
      }
      
      int main(){
          std::vector<int> myVec={1,2,3};
          Function(myVec);
      }
      
  5. A pointer to a class/struct uses -> to access its members, whereas a reference uses . (Same as python.) What are the differences between a pointer variable and a reference variable? - SO

Ref:

  1. Pointers vs References in C++ - GeeksforGeeks

Raw Array

(2023-11-11)

  1. The variable name exampleArray is an address of the starting byte, so it’s a pointer.

    1
    
    f = 
    
  2. Array is a row of contiguous memory.

     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
    
    #include <iostream>
    int main(){
        // Create an array on stack, which
        // will be destoryed when getting out of the scope
        int exampleArray[4];
        for (int i=0; i<5; i++)
            exampleArray[i] = i;
    
        cout << exampleArray << " is the address of the array" << endl;
        cout << &(exampleArray[0]) << " is the address of the first element" << endl;
    
        cout << *exampleArray << " is the first element by dereferencing the pointer" << endl;
    
        // Get the 3rd elements can do arithmatic on pointer,
        // where the size of the type will be multiplied automatically.
        cout << *(exampleArray + 2) << " is the 3rd element"<< endl;
    
        // If access the 2nd elements by calculating in bytes,
        // cast integer pointer to char pointer with is 1-byte type.
        // After locating the address, cast back to integer pointer for assigning a integer value.
        *(int*)((char*)exampleArray + 8) = 30;
        cout << exampleArray[2] << " is the modified 3rd element" << endl;
    
        cout << sizeof(exampleArray) << " bytes taken by the entire array"<< endl; 
    
        // For array created on stack, the number of elements can be known because stack pointer contains offset:
        int num_elements = sizeof(exampleArray) / sizeof(int);
        cout << num_elements << " elements in the array on stack" << endl;
    
        // Create an array on heap with the `new` keyword, 
        // This array is the same as exampleArray, except for its lifetime that lasts until calling `delete[] arrayname`. 
        // So if an array created inside function needs to be returned, it must be created on heap
        int* exampleArray_heap = new int[4]; // is a pointer (address 0x55555556b2c0) to heap, not the 1st element 0,
        for (int i=0; i<4; i++)
            exampleArray_heap[i] = i;
    
        // Read the "address" from memory in bytes
        std::cout << std::hex << *(long long*)&exampleArray_heap << std::endl;
    
        cout << &exampleArray_heap << " stores the 8-byte address (the pointer) to the array" << endl;
        cout << exampleArray_heap << " is the address (pointer) to the array on heap" << endl; // cast type to integer pointer
        cout << *(exampleArray_heap+0) << " is the first element in the array" << endl;
        // one more jump will reduce performance
    
        // exampleArray_heap is a pointer of pointer (memory indirection). An 32-bit address taking 4 bytes
        cout << sizeof(exampleArray_heap) << " bytes for the pointer of the array on heap" << endl;   //
    
        delete[] exampleArray_heap;
    
    
        // If array is not created on stack, or only has the pointer to an array, 
        // the number of elements can't be computed as sizeof(array)/sizeof(int). So it must be maintained. 
        static const int exampleSize = 5; // must be known at compile-time
        int myArray[exampleSize];
    }
    
    0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 2 0 0 0 0 0 0 b f 5 6
  3. Create an array on heap.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    class Entity{
    public:
        int* exampleArray_obj = new int[4];
    
        Entity(){    // construction function
            for (int i=0; i<4; i++)
                exampleArray_obj[i] = i;
        }
    };
    
    int main(){
       Entity e;
       cout << e.exampleArray_obj << " is the address (pointer) to array" << endl;
       cout << *(e.exampleArray_obj) << " is the 1st element of array" << endl;   // 0
    }
    
  4. std::array has .size().

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    #include <iostream>
    #include <array>
    
    int main(){
        std::array<int, 4> myArray;
    
        for(int i=0; i<myArray.size(); i++)
            myArray[i] = i;
    }
    

Ref:

  1. Arrays in C++ - The Cherno

Pointer Alignment

(2023-11-14)

(Unsure about my naming for this.)

Because the algorithm specifies processing 128 data at a time, i.e., the factor alignment = 128. Thus, each time 128*sizeof(dtype) memory will be accessed.

Therefore, the pointers to data should all be multiples of 128.

The way to round a number to a multiple of 128 is setting its last 8 bits to 1000_0000 (128).

  1. The type of pointer affects its arithmetic:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    #include <iostream>
    int main(){
      // int-type pointer:
      int* chunk = nullptr;    // 8-byte address: 0 0 0 0 0 0 0 0
      int* ptr = chunk + 127;  // 0 + 127*sizeof(int) = 508 = 0x1fc
      std::cout << ptr << std::endl; // 0x1fc
    
      // char-type pointer:
      char* ptr2 = (char*) chunk + 127; // 0 + 127*sizeof(char) = 127 = 0x7f
      std::cout << static_cast<void*>(ptr2) << std::endl; // 0x7f
    }
    
  2. Convert a pointer to an integer: SO

    1
    2
    
    char* chunk = nullptr;
    uint var = reinterpret_cast<std::uintptr_t>(chunk); // ok
    
  3. Ensure offset a multiple of 128:

    If the currect pointer chunk is not a multiple of 128, add another 127, and then truncate the last 8 bits to 1000_000 to make it a multiple of 128.

    1
    2
    3
    4
    5
    6
    
    int main(){
      char* chunk = (char*)130;
      std::size_t alignment = 128;   // uint
      // 130 + 127 & ~127 = 256
      std::size_t offset = (reinterpret_cast<std::uintptr_t>(chunk) + alignment - 1) & ~(alignment - 1);  // uint
    }
    

    Bitwise operation:

    1
    2
    3
    4
    5
    
    chunk:                 0000_0000_1000_0010
    alignment-1:           0000_0000_0111_1111
    ~(alignment-1):        1111_1111_1000_0000
    chunk + (alignment-1): 0000_0001_0000_0001  (257)
    (257)&~(127) = 256:    0000 0001 0000 0000
    

    Code from 3DGS

Possible related articles:

  1. What exactly is an ‘aligned pointer’?

size_t

(2024-01-24)

  • size_t is the unsigned integer type. For example, if it’s 8 bytes (64-bit unsigned long), its value ranges in [0, 2⁶⁴-1]. In other words, if a number is larger than 2⁶⁴-1, it can’t be declared as the size_t type.

  • Usage: Given a variable that stores the number of elements of an array, or number of bytes of an object, it can be set as the size_t type, as the name indicated that size_t is used for representing a “size”.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <cstddef> // For size_t
#include <typeinfo>
int main() {

    size_t arraySize = 10;  // unsigned int (16 bits at least)
    int myArray[arraySize]; // a 10-integer array

    size_t sizeOfArray = sizeof(myArray);   // 40 bytes

    // The type of `arraySize` is unsigned integer
    const std::type_info& typeInfo = typeid(arraySize);
    std::cout << typeInfo.name() << std::endl;  // return: m
}
  • m refers to unsigned long integer. The returned strings are defined differently across various compilers. So, the correct meaning should be found in the implementation. Strange output of std::typeid::name() - SO

Do not use typeid

The returned string is not consistent; Need to check the documents.

Ref: typeid(xxxx).name() “m” returned? - C++ Forum


Reserve Memory

(2024-01-26)

  1. Pad the lastCount value to a multiple of the alignment 128, and then allocate the following count bytes, which is the number of elements of the target data pointed by dataPtr:

     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
    
    // "reserve_memory.cpp" 
    #include <torch/torch.h>
    template <typename T> // T can be int, float..., deduced when compiling
    void accum(char*& lastCount, T*& dataPtr, size_t count, size_t alignment) {
    
        // Decimal arithmetic
        size_t offset = (reinterpret_cast<std::uintptr_t>(lastCount) + alignment-1) & ~(alignment-1);
    
        // Update the pointer to the data to be filled
        dataPtr = reinterpret_cast<T*>(offset);
    
        // Accumulate the number of bytes of the data
        lastCount = reinterpret_cast<char*>(dataPtr+count);
    }
    
    int main(){
        char* size = nullptr;   // Count from 0x0
        float* depth;    // sizeof(float) = 4 bytes
        float* color;
        accum(size, depth, 100, 128);   // 0 + 100*4 = 400 bytes = 0x190 bytes
        printf("%p \n", size);          // 0x190
        accum(size, color, 100*3, 128); // 512 + 300*4= 1712 bytes = 0x6b0 bytes
        printf("%p \n", size);          // 0x6b0
        return 0;
    }
    
    • char*& size = nullptr: the variable size is an alias of the nullptr, sharing the same memory.

      Such that the pointer (lastCount and dataPtr) is changed directly.

  2. Compilation. Ref: Troubles while compiling C++ program with PyTorch, HElib and OpenCV - SO

    1
    2
    3
    4
    5
    6
    7
    8
    
    g++ -g -std=c++17 \
    -I/usr/local/libtorch/include \
    -I/usr/local/libtorch/include/torch/csrc/api/include \
    -I/usr/local/libtorch/include/torch \
    -L/usr/local/libtorch/lib \
    -Wl,-rpath,/usr/local/libtorch/lib \
    reserve_memory.cpp \
    -ltorch -ltorch_cpu -lc10 # Order matters: After .cpp file
    
    • doubt: If I add -O2 option, the libraries -ltorch -ltorch_cpu -lc10 can be omitted. Don’t know why. With -O2 optimization, debugging will become difficult.

    • The original anwser:
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      
      g++ -g -O2 -std=c++17 \
      -pthread \
      -march=native \
      -I/home/lulu/helib_install/helib_pack/include \
      -I/usr/include/opencv4 \
      -I/home/lulu/libtorch/include \
      -I/home/lulu/libtorch/include/torch/csrc/api/include \
      -I/home/lulu/libtorch/include/torch \
      -L/home/lulu/helib_install/helib_pack/lib \
      -L/usr/include/opencv4 \
      -L/home/lulu/libtorch/lib \
      -Wl,-rpath,/home/lulu/libtorch/lib \
      prova.cpp \
      -lopencv_core -lopencv_highgui -lopencv_imgcodecs \
      -lhelib -lntl -lgmp -lm \
      -ltorch -ltorch_cpu -lc10 \
      -o prova
      
Built with Hugo
Theme Stack designed by Jimmy