LINUX.ORG.RU

Передача параметров по ссылке. Что не так с моей программой?

 , , , ,


0

1

Есть вот такая программа:

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>

class no_object : public std::exception
{
protected:
    std::string mMsg;
public:
    no_object(const char* msg) : mMsg(msg) {}
    virtual ~no_object() noexcept override {}
    virtual const char* what() const noexcept override { return mMsg.c_str(); }
};


using namespace std;

class Object {
public:
    Object(const string info) : information(info) { cout << "Object constructor!" << endl; }
    ~Object() { cout << "Object destructor!" << endl; }
    Object(const Object& obj) { information = obj.information; cout << "Copy constructor!" << endl; }
    void setInformation() {  }
    string getInformation() const { return information; }
private:
    string information;
};

class Storage {
public:
    Storage(const size_t width) {
        objs = static_cast<Object*>(malloc(sizeof(Object) * width));

        if (objs == NULL)
            throw std::bad_alloc();

        lastPointer = objs + sizeof (Object) * (width - 1);
    }

    void storeObject(Object& obj, size_t index) {
        if (isIndexOutOfRange(index))
            throw std::out_of_range("Oops, index is out of range!");

        availableIndexes.push_back(index);
        objs[index] = obj;
    }

    Object& getObjectAtIndex(size_t index) const {
        if (isIndexOutOfRange(index))
            throw std::out_of_range("Oops, index is out of range!");

        auto it = find(availableIndexes.begin(), availableIndexes.end(), index);
        if (it == availableIndexes.end())
            throw no_object("Oops, the object for this index is not set!");

        return objs[index];
    }

    ~Storage() {
        free(objs);
    }
private:
    bool isIndexOutOfRange(size_t index) const noexcept {
        Object* indexPointer = objs + sizeof (Object) * index;

        if (indexPointer > lastPointer)
            return true;

        return false;
    }

    vector<size_t> availableIndexes;

    Object* objs;
    Object* lastPointer;
};

int main()
{
    Storage storage(3);
    {
        cout << "1" << endl;
        Object obj = Object("lambo");
        cout << "2" << endl;
        Object& objRef = obj;
        cout << "3" << endl;
        storage.storeObject(objRef, 2);
    }

    cout << "4" << endl;
    Object savedObject = storage.getObjectAtIndex(2);
    cout << "Information from stored object is " << savedObject.getInformation() << endl;

    return 0;
}

Вывод который она выдает:

1
Object constructor!
2
3
Object destructor!
4
Copy constructor!
Information from stored object is lambo
Object destructor!

По ней у меня есть несколько вопросов?

1. Мы в памяти сохранили объект который получили по ссылке перед тем как она вышла из области видимости. При условии что этот объект получаю из памяти, можно ли так делать? Если нет то как это исправить. Только просьба не советовать stl и vector. Я именно хочу разобраться глубоко при работе с памятью.

2. Есть ли какой-то undefined behavior в ее использовании кроме копирования объекта Storage?

Заранее благодарен за ответы и если можете скинуть ссылки где можно прочитать про нечто подобное, а именно использование подобной схемы управления памятью буду очень признателен.


Ответ на: комментарий от anonymous

Ладно, давай EOT, потому что толку от твоих последних ответов, что от козла молока (я не говорю про самые первые). Тебе спасибо за то, что показал, где я чего-то не знаю.

dissident ★★
()
Ответ на: комментарий от anonymous

Ты как раз таки попал пальцем в ж... потому что, лечиться тоже надо https://github.com/oguzhaninan/Stacer dashboard не тот disk, что надо (комментарий). Но это опять же никого не е...т.

В любом случае спасибо. За ссылки и за пожелание удачи.

dissident ★★
()
Ответ на: комментарий от anonymous

Я достану этот трэд из мусорки и только закину линк (я не успокоился и спросил еще и на stackoverflow):

https://stackoverflow.com/questions/53451770/treating-memory-returned-by-oper...

Уже в первом ответе в линке есть такой же пример как у Саттера и действительно он может быть UB. Однако:

In practice, this code works across a range of existing implementations

(с) http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0593r2.html

dissident ★★
()
Последнее исправление: dissident (всего исправлений: 1)
Ответ на: комментарий от dissident

и действительно он может быть UB.

А можно для тупых пояснить, в чём именно там ub? Я так понял, что все проблемы из-за того, что совершается арифметика над памятью с не инициализированными объектами. Но не понял, почему это ub. Инициализация объекта ведь никак на память не повлияет.

Или я всё понял не так?

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Я так понял, это какие-то подобные проблемы, какие имеют писатели законов и регуляций налогов. Второй ответ хорошо это объясняет:

The C++ standards contain an open issue that underlying representation of objects is not an «array» but a «sequence» of unsigned char objects. Still, everyone treats it as an array

Issue вот: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1701

Доказательство что pointer arithmetic safe вот (из того же ответа, я просто как попугай за ним повторяю): http://eel.is/c draft/expr.sizeof#2 а именно строка: When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.

Т.е. проблема похоже в самом стандарте и объектной модели, к которой есть issue, требующий поправить саму объектную модель. Если я все правильно понял.

PS По поводу того что UB из-за alignment, из того же трэда:

You can do it with «old fashioned» malloc, which gives you a block of memory that fulfils the most restrictive alignment on the respective platform (e.g. that of a long long double)

и из другого ответа:

• **Is pointer arithmetic undefined behavior when used over memory returned by operator new()?**

Nope, as long as you respect the process' memory boundaries — from this point of view new basically works the same way as malloc, moreover, new actually calls malloc in the vast majority of implementations in order to acquire the required area.

Т.е. проблема в недопиленной объектной модели.

Могу и ошибаться.

PPS Вот с placement array new там действительно косяки потому что оно хочет больше места, чем выделенно было malloc/operator new.

dissident ★★
()
Последнее исправление: dissident (всего исправлений: 1)
Ответ на: комментарий от dissident

До чего бываю падкие на авторитеты люди. Если высокоранговая макака делает X, значит X — это правильно. А что элементарными рассуждениями можно показать что X ошибочно, на них не влияет. Наличие высокоранговой макаки отключает мозги напрочь.

Ну и ты отметил не тот ответ как верный. Ответ Брайана Би лучше. А отмеченный ответ содержит фактические ошибки, вроде того, что http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1701 имеет отношение к обсуждаемой теме и что есть намерение считать представление объекта массивом.

Есть намерение считать возвращаемую операторами выделения памяти память массивом. Но насчёт представления объекта не в динамическом сторадже, намерения считать память под ним массивом, насколько мне известно, нет, а есть обратное намерение.

P.S. у тебя ошибка:

However I was then pointer

However I was then a pointer

anonymous
()
Ответ на: комментарий от dissident

Если я все правильно понял.

Ты как обычно понял всё неправильно.

Даже если мы представим, что CWG1701 исправят так, что представление объекта будет считаться массивом, арифметику над указателем, возвращённым из operator new, это не разрешит, т.к. эта память не является представлением никакого объекта.

Чтобы она была разрешена, нужно постулировать, что operator new и прочие malloc-и возвращают память с созданным в них массивом unsigner char соответствующего размера.

anonymous
()
Ответ на: комментарий от anonymous

Я опять ничего не понял из твоих ответов.

Из ответов на stackoverflow что-то светиться иногда, из твоих ничего.

dissident ★★
()
Ответ на: комментарий от anonymous

Давай начнем с того, что если бы malloc нельзя было использовать так как выше, то в C не было бы динамических массивов.

Далее продолжим тем, что в C++ operator new часто = malloc, видел своими глазами в бородатом gcc.

И тем что C++ все же коим-то образом совместим с C.

Вывод: если арифметика будет работать с malloc, то должна и с operator new.

dissident ★★
()
Ответ на: комментарий от dissident

Давай начнем с того, что если бы malloc нельзя было использовать так как выше, то в C не было бы динамических массивов.

Давай начнём с того, что в C нет чётко определённой модели памяти и на этом же можно закончить.

Дальше ты пишешь бред, который ниоткуда логически не следует.

anonymous
()
Ответ на: комментарий от anonymous

Я ж не против, что я дурак, понял неправильно, падок на авторитеты, макака, алкоголик и придурок. Это не меняет того, что у тебя не получается разложить проблему на элементарные блоки и так ее представить. Ты ссылаешься на стандарт, который говорит UB. Почему UB? Что конкретно не так? sizeof(T[N]) = sizeof(T)*N. В C malloc подходит для динамических массивов. Я уверен, что там есть РЕАЛЬНЫЕ причины для UB, а не «стандарт говорит». Их и интересно узнать.

PS

Чтобы она была разрешена, нужно постулировать, что operator new и прочие malloc-и возвращают память с созданным в них массивом unsigner char соответствующего размера.

Они там создаются потом placement (не array) new. Они aligned. N*aligned = aligned. Размер равен N*laligned. Его хватает, хоть это и не массив (placement array new выделяет больше памяти и это массив). Скакать можно. Мы сами скачем, потому что знаем куда. Все работает. То, что они не объекты созданные в массиве, что не представляет проблемы в C (ввиду отсутствия объектов в C), и то, что это проблема должно иметь корни и причины почему такого нельзя делать.

dissident ★★
()
Последнее исправление: dissident (всего исправлений: 3)
Ответ на: комментарий от dissident

Я уверен, что там есть РЕАЛЬНЫЕ причины для UB, а не «стандарт говорит».

Реальные причины для UB в том, что нельзя определить арифметику указателей кроме как для итерации по элементам массива, не делая абстрактную машину C++ конкретной (привязаться к определённой архитектуре, ОС и т.д.).

anonymous
()
Ответ на: комментарий от anonymous

Спасибо!

И самый главный вопрос: а почиму? Если потому что можно представить себе конкретную имплементацию в которой память выглядит как картофель, посаженный квадратно-гнездовым методом или как биологический компьютер, то это опять же проблемы писателей стандарта.

Если же для того, чтобы это работало каждый vector'о писатель должен использовать какие-то нестандартные фичи компилятора, то инетересно почему и какие.

dissident ★★
()
Последнее исправление: dissident (всего исправлений: 1)
Ответ на: комментарий от Ivan_qrt

Но не понял, почему это ub.

Потому что так написано в стандарте.

anonymous
()
Ответ на: комментарий от dissident

Ну т.е. по факту никакого ub там быть не может, т.к. инициализация объектов в куске памяти, никак на этот кусок не повлияет. И всё, что будет работать после инициализации, будет так же работать и до неё. Речь про адресную арифметику, разумеется.

Тогда можно считать это упущением в стандарте и не забивать голову.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Может компилятор именно в C++ именно operator[] для array как-то иначе считает, чем operator+() для указателей. Хотя зачем?

Anonymous объяснил так, что ничего не понятно.

Stackoverflow объяснил так, что ничего не понятно, но деталей больше.

dissident ★★
()
Ответ на: комментарий от dissident

Может компилятор именно в C++ именно operator[] для array как-то иначе считает, чем operator+() для указателей. Хотя зачем?

Да быть такого не может. Тогда были бы отличия, а оно везде взаимозаменяемое. Да и operator[] раскрывается в простую адресную арифметику.

В общем-то в одной части стандарта операции с указателями не определены для не инициализированной памяти. Но исходя из других частей поведение вполне однозначное, и сделать что-то, что будет соответствовать стандарту, но при этом будет как-то не так считать указатели для голой памяти, я так понимаю, невозможно.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Но исходя из других частей поведение вполне однозначное

Можно увидеть эти части и узнать, какое же там поведение?

anonymous
()
Ответ на: комментарий от anonymous

Можно увидеть эти части и узнать, какое же там поведение?

Для правильно выравненного не инициализированного куска памяти соответствующего размера поведение такое же, как и для массива.

Следует это из того, что поведение определено для массива, соответственно оно определено для куска памяти, на котором для всех эл-тов последовательно вызвали конструкторы (array object'ы размера 1). Т.к. конструкторы не могут повлиять на низлежащую память (изменить выравнивание, переаллоцировать, короче хоть как-то повлиять на адресную арифметику), то чтобы адресная арифметика нормально работала с таким массивом, она обязана нормально работать с неинициализированной памятью. Т.о. реализация, в которой адресная арифметика для массива будет корректной, а для правильного не инициализированного куска памяти - нет, невозможна.

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

Ты написал, что есть части стандарта, откуда что-то там следует. А когда я попросил их показать, ты зачем-то начал нести отсебятину.

Можно увидеть части стандарта, а не отсебятину?

anonymous
()
Ответ на: комментарий от anonymous

Можно увидеть части стандарта, а не отсебятину?

На что именно из отсебятины тебе нужно подтверждение из стандарта?

Ivan_qrt ★★★★★
()
Ответ на: комментарий от Ivan_qrt

На что именно из отсебятины тебе нужно подтверждение из стандарта?

Дурака не валяй. Написал:

В общем-то в одной части стандарта операции с указателями не определены для не инициализированной памяти. Но исходя из других частей поведение вполне однозначное

Перечисляй «другие части», из которых там якобы что-то исходит.

anonymous
()

Object& objRef = obj;
cout << «3» << endl;
storage.storeObject(objRef, 2);

просто

storage.storeObject(obj, 2);

не надо создавать отдельную ссылку в данном случае. Объект передастся по ссылке в метод storeObject.

Object obj = Object(«lambo»);

можно проще:

Object obj("lambo");

ну и да, у тебя указатель на последовательность ячеек памяти Object* objs как то странно начинает размещение в памяти через malloc, проще и лучше же создать динамический массив через 

Object* objs;
size = (число);
objs = new Object[size];


вроде все, на что я могу обратить внимание

safocl ★★
()

ах да, у тебя же еще и нет конструктора по умолчанию для class Object... по ентому для инициализации объектов в массиве надо будет его создать.

safocl ★★
()
18 апреля 2020 г.
Ответ на: комментарий от Ivan_qrt

Туда только недавно добавили неявное создание объектов.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.