0

I'm having real difficulty understanding what is going on with memory allocation two specific scenarios that are producing different result but functionally appear identical to me when applying my naive wisdom. I created a simplified anologous code example below to demonstrate the issue I am encountering. I appreciate that that data is being passed to functions in an overaly complex way but please assume it is necassary unless fundamentally flawed.

In the first section of comment code. This works, no issue. In the second section of the commented code, the terminal flashes for a moment and the drops out with no errors or warnings.

I'd really appreciate an understanding of why this is occuring. Clearly I still have much to learn!

My thanks in advance,

Matminster.

#include <iostream>
#include <list>

struct item{
    int item_ID;
    std::list<int> related_item_IDs;
};

struct item * return_item_details(std::list<item> list_of_items, int item_ID) {
    
    for(std::list<item>::iterator it = list_of_items.begin(); it != list_of_items.end(); it++) {
        if(it->item_ID == item_ID) {
            return & * it;
        }
    }
    return NULL;
}

int find_shared_related_items(int item_1_ID,int item_2_ID, std::list<item> list_of_items) {
    int total_shared_items=0;
    
    //This works_________________________________________________________________
//    struct item * item_1_pointer = return_item_details(list_of_items, item_1_ID);
//  struct item * item_2_pointer = return_item_details(list_of_items, item_2_ID);
    //___________________________________________________________________________
    
    //But not this?________________________________________________________________
//  struct item * item_1_pointer = (struct item * ) malloc(sizeof(struct item * ));
//  item_1_pointer = return_item_details(list_of_items, item_1_ID);
//  struct item * item_2_pointer = (struct item * ) malloc(sizeof(struct item * ));
//  item_2_pointer = return_item_details(list_of_items, item_2_ID);
//  //_____________________________________________________________________________

    for(std::list<int>::iterator it = item_1_pointer->related_item_IDs.begin(); it != item_1_pointer->related_item_IDs.end(); it++) {
        for(std::list<int>::iterator it2 = item_2_pointer->related_item_IDs.begin(); it2 != item_2_pointer->related_item_IDs.end(); it2++) {
            if( * it == * it2) {
                total_shared_items++;
            }
        }
    }
    
    return total_shared_items;
}


int main() {
    
    item item_1; 
    item_1.item_ID = 1;
    
    item_1.related_item_IDs.push_back(3);
    item_1.related_item_IDs.push_back(4);
    item_1.related_item_IDs.push_back(5);
    item_1.related_item_IDs.push_back(6);
    
    item item_2; 
    item_2.item_ID = 2;
    
    item_2.related_item_IDs.push_back(5);
    item_2.related_item_IDs.push_back(6);
    item_2.related_item_IDs.push_back(7);
    item_2.related_item_IDs.push_back(8);
    
    std::list<item> list_of_items;
    list_of_items.push_back(item_1);
    list_of_items.push_back(item_2);
    
    std::cout << "The total number of shared items between item " << item_1.item_ID << " and item " << item_2.item_ID << " is: " << find_shared_related_items(item_1.item_ID, item_2.item_ID, list_of_items) << "\n";
    
    return 0;
}
  • Run your program with the debugger and you will see where it crashes. – user253751 Aug 11 '20 at 15:36
  • 2
    `malloc` on non-POD class (due to it containing `std::list` as a member)? That's undefined behavior. Even if it did work, why would one allocate memory, store the memory in a pointer, and then, immediately, overwrite said pointer? – Algirdas Preidžius Aug 11 '20 at 15:36

1 Answers1

0

The first version works by sheer misfortune. It's an Undefined Behaviour.

In return_item_details you accept std::list by value, which means the list and all its elements are copied and will be destroyed once the function finishes. You then return address of one of those temporary elements and try to use it (but the element doesn't exist anymore).

You can change the parameter to be passed by reference:

struct item * return_item_details(std::list<item>& list_of_items, int item_ID) {
//                                ~~~~~~~~~~~~~~~^

Or use std::find_if from <algorithm> library instead:

auto item_1_iter = std::find_if(list_of_items.begin(), list_of_items.end(), 
                                [item_1_ID](auto& listElem){return listElem.item_ID == item_1_ID;});

Couple of extra notes:

  1. Your second version did the same thing as the first, but it added a memory leak. You call malloc and immediately overwrite the pointer to the newly allocated memory, losing it.
  2. Don't use malloc in C++ (unless you are ready to use placement new afterwards). The C++ way to allocate memory is using new and delete.
  3. Avoid using new and delete in modern C++. We have smart pointers and containers to do that for us. In C++11 there are few places where you need new, in C++14 it's already a bit of a bad smell.
  4. struct item * is a C-ism. You don't need to repeat struct keyword in C++, item* item_1_pointer = nullptr; will work just fine.
  5. auto is a very handy mechanism introduced in C++11, it may allow you to skip typing out std::list<item>::iterator (and any other type than compiler can deduce from context) explicitly:
for(auto it = item_1_pointer->related_item_IDs.begin(); it != item_1_pointer->related_item_IDs.end(); it++) {
    for(auto it2 = item_2_pointer->related_item_IDs.begin(); it2 != item_2_pointer->related_item_IDs.end(); it2++) {
        if( * it == * it2) {
            total_shared_items++;
        }
    }
Yksisarvinen
  • 18,008
  • 2
  • 24
  • 52
  • Wow thanks. I assumed without realising that a list was automatically passed by reference, much like a vector. I'll give it a go now but already this makes perfect sense. The second part about using the algorithm library is a bit beyond me at the moment, so I will keep that in mind and revisit this once I started learning that stl. – Matminster Aug 11 '20 at 15:46
  • @Matminster You're welcome. I'm not sure if you are used to C programming or your teacher is, but C++ became quite different in 2011. You mentioned in a comment that you are using C++11, but you don't use a single feature of that standard and most of the code looks like C due to things like `struct item` and `malloc`. Don't get me wrong, I just want to point out that your learning source might be outdated. – Yksisarvinen Aug 11 '20 at 15:54
  • Yeah, understood. I think that problem is primarily my fault. I'm trying to learn as much as possible so I have been intentionally taking away modern abstractions/features (that clearly make coding more robust and less error prone) in order to understand some of the inner workings. I almost feel like I'm getting an easy ride by only exposing myself to modern C++. What you explained above has been incredibly useful and allowed a number of things that I have learnt to "click into place". Oh, and obviously it also worked when I tried it. Thanks again, I very much appreciate all of this detail! – Matminster Aug 11 '20 at 16:02
  • @Matminster Ah, that's a good thing then, I also started with the non-modern C++ and it helps a lot later (once you get through the hurdle of "how the hell does everything work"). If you need some help, there's a [list of good C++ books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) here on SO with something for all the flavours of C++ (old, modern, beginner, advanced...), perhaps you can find something for yourself there. – Yksisarvinen Aug 11 '20 at 16:10