profiles
profiles copied to clipboard
Simple idea (and question)
I was thinking about memory safety recently but I'm still learning C++ and I'm not an advanced user but based on all the lectures I've watched I had this simple idea but I'm not sure how useful it could be. The programming languages provide memory safety usually by not allowing memory errors to occur(like GC or Borrow checker) but C++ currently works with a mix of strategies, like smart pointer and static analyzers etc. It seems to me that avoiding memory errors is not really necessary if the language could just spot and notify the user of all memory errors and and related problems. Completely avoiding memory error have the cost either making programming harder or in runtime performance.
One strategy that could help is to have double versions of memory unsafe libraries and features. One version would be just to debug the code, where more verifications related to memory safety could be performed and other version would be the production one, to be used after all related memory issues were fixed. This way we could put much more code to check for memory safety inside librarys like bounds checking in all index operations, std::shared_pointer could perform more checks like cyclic reference, etc. In debug mode even the c++ runtime could perform some checks. That would impact the program performance but just in the debug mode. After all issues were fix it would be just a matter of use some -DNDEBUG flag and recompile the code and we would have a correct and performant version running. Adding just bounds checking to everything would not be a complicated process because the duplicated library would not be so much different from the current ones in the standard and thus not hard to maintain.
If the code could detect all memory errors it could be just a matter to use some fuzzing to the program inputs and all errors could be found. I wrote this simple Array example in two versions just to illustrate the idea. Do you think this approach could be useful?
#include <iostream>
#include <source_location> // From C++23 - to capture the file line number where function is called.
#define DEBUG
#ifdef DEBUG
// **Debug version - bounds checking**
template<typename T, size_t size = 0>
class Array
{
public:
constexpr auto operator[] (int index, std::source_location const& location = std::source_location::current()) -> T&
{
if(0 <= index && index < static_cast<int>(m_size)) {
return arr[index];
}
else {
std::cout << "Error: Array overflow at line " << location.line() << '.' << std::endl;
exit(1);
}
}
private:
T arr[size];
size_t m_size = size;
};
#else
// **production version - no bounds checking for performance.**
template<typename T, size_t size = 0>
class Array
{
public:
constexpr auto operator[] (size_t index) -> T&
{
return arr[index];
}
private:
T arr[size];
size_t m_size = size;
};
#endif
auto main() -> int
{
// There would be no change in syntax
Array<int, 3> myArray;
myArray[0] = 11;
myArray[1] = 12;
myArray[2] = 13;
myArray[3] = 14; // error: overflow
std::cout << "myArray[0]: " << myArray[0] << std::endl;
std::cout << "myArray[1]: " << myArray[1] << std::endl;
std::cout << "myArray[2]: " << myArray[2] << std::endl;
return 0;
}