Как сделать так, чтобы пользователь не мог удалить динамический массив?

#c #pointers #dynamic-allocation

#c #указатели #динамическое распределение

Вопрос:

При написании ответа я написал некоторый код, который оспаривал мои предположения о том, как работают указатели const . Я предполагал, что указатели const не могут быть удалены функцией delete, но, как вы увидите из приведенного ниже кода, это не так:

 #include <new>
#include <string.h>

class TestA
{
    private:
        char *Array;
    public:
        TestA(){Array = NULL; Array = new (std::nothrow) char[20]; if(Array != NULL){ strcpy(Array,"Input data"); } }
        ~TestA(){if(Array != NULL){ delete [] Array;} }

        char * const GetArray(){ return Array; }
};

int main()
{
    TestA Temp;
    printf("%sn",Temp.GetArray());
    Temp.GetArray()[0] = ' '; //You can still modify the chars in the array, user has access
    Temp.GetArray()[1] = ' '; 
    printf("%sn",Temp.GetArray());
    //Temp.GetArray() = NULL //This doesn't work

    delete [] Temp.GetArray(); //This works?! How do I prevent this?
}
  

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

Комментарии:

1. Вы никогда не «удаляете указатель». Вы удаляете объект, на который указывает указатель.

Ответ №1:

Если ваши пользователи используют delete[] указатели on, которые они не получили new[] , ударьте их по голове битой-подсказкой.

Существует так много причин, по которым указатель может быть разыменован, но не должен передаваться на удаление:

  • Кто-то другой удалит его.
  • Это не начало блока.
  • Он пришел из malloc или какого-либо другого не new распределителя.
  • Он имеет статическое, а не динамическое время жизни.
  • If имеет автоматическое время жизни.

Некоторые из них будут проявляться в исключениях во время выполнения. Другие приведут к сбоям в более позднее время. Все это «неопределенное поведение» в соответствии со стандартом.

Предполагается, что указатель не может использоваться в качестве аргумента delete , если явно не указано иное.

Если программисты начального уровня совершают эту ошибку, обучите их. Если это делают «опытные» разработчики, увольте их за ложь в их резюме.


Если вы просто хотите, чтобы при этом было больно, выделите массив на единицу больше, чем необходимо, и return Array 1; . Теперь все взорвется, если они попытаются использовать delete[] .

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

Комментарии:

1. Не неопытные пользователи, а злонамеренные. Однако мне нравится ваш трюк с массивом 1, поскольку это предлагает практическое решение (т. Е. Программа не будет продолжать работать). Я не буду реализовывать это в этой версии charArray, но в следующей редакции это будет. Я добавлю, что может потребоваться два указателя: указатель на адрес памяти для удаления, который никогда не возвращается, и один на смещение 1. Верните смещение 1 и bam.

2. @SSight3: не рассматривайте это как какую-либо защиту от злоумышленников. Нетрудно получить доступ к вашему классу и перезаписать ваш частный указатель. Единственное, что можно сделать с ненадежным кодом, это запустить его в поддерживаемой ОС (с помощью модуля защиты памяти) изолированной среде.

3. @SSight3: вы все равно можете delete [] (Test.GetArray()-1); , если пользователь видит ваш внутренний указатель. И он также может просто отредактировать вашу реализацию. Не теряйте голову над этим, просто сделайте самую простую и быструю вещь.

4. @Ben: Отметил. Я полагаю, что в некотором смысле вы мало что можете сделать, но все помогает научить меня чему-то новому. Тем не менее, трюк все еще нравится.

Ответ №2:

 delete [] Temp.GetArray(); //This works?! How do I prevent this?
  

До тех пор, пока он возвращает char* или какой-либо другой тип указателя, вы не можете предотвратить это; не имеет значения delete , является ли выражение const inзаявлением, потому что все следующее совершенно справедливо в C :

 char *pc = f(); 
delete [] pc;  //ok

const char *pc = g(); 
delete [] pc; //ok

char * const pc = h(); 
delete [] pc; //ok

const char * const pc = k(); 
delete [] pc; //ok
  

Однако, если вы измените это :

 char *Array;
  

к этому

 std::vector<char> Array;
  

И тогда вы могли бы достичь того, чего хотите, вернув его как:

 std::vector<char> amp; GetArray() { return Array; }
  

Нижняя строка :

В C динамический массив должен быть выбран по умолчанию, std::vector<T> если у вас нет веских причин не использовать его.

Комментарии:

1. @BenVoigt Но теперь он может возвращать постоянную ссылку, размер которой не так легко изменить.

2. @SSight3: delete [] Test.data(); — ну, вы можете быть настолько параноиком, насколько хотите, но когда пользователь делает это, это его собственная вина, вы ничего не можете сделать. Еще лучшим выбором было бы не возвращать массив (или вектор), а просто обернуть доступ соответствующим operator[] , но даже тогда пользователь может делать глупости вроде delete [] amp;Temp[0]; . Вы могли бы вернуть прокси-сервер из operator[] , который перегружает operatoramp; или что-то в этом роде, но суть в том, что вы не можете помешать вашему пользователю стрелять себе в ногу. Единственное, что вы можете сделать, это усложнить им задачу.

3. @SSight3 Так что тогда не пишите delete[] Test.data() . Тот, кто это пишет (что теперь явно намеренно), сам несет ответственность за проблемы (и я уверен, что в любом случае вызывает UB по стандарту).

4. @SSight3: Точно. std::vector делает это на один шаг сложнее. Кроме того, при программировании на C следует знать, что std::vector он реализует RAII, что означает, что он сам заботится об управлении памятью, и пользователь не должен беспокоиться об этом. Теперь, если пользователь пишет delete []Test.data() или delete []amp;Test[0] , то либо он сошел с ума, ЛИБО он должен прочитать хорошую книгу по C .

5. @SSight3 Такой пользователь мог бы также просто написать int i = 7; delete amp;i; и аплодировать самому себе за вызов UB, что бы еще это ни дало ему.

Ответ №3:

Ваш const не дает никакого эффекта, вы возвращаете указатель const, а не указатель на const char , чего вы и хотите. Также см. Рекомендацию Nawaz по использованию векторов вместо этого.

Комментарии:

1. Не в стиле понижения голосов, я объясню, почему вы ошибаетесь: Temp.GetArray() = NULL; предотвращается (как было бы новым и т. Д. И т. Д.) С помощью указателя const (Т.Е. Адрес не может быть переназначен вне класса). Вам не нужен постоянный символ, поскольку, учитывая пример, предполагается, что пользователь может изменять содержимое массива, а не сам массив.

2. @SSIght3: Temp.GetArray() = NULL; будет работать, только если вы вернете ссылку на указатель. При возврате указателя по значению оригинал не может быть перезаписан, независимо от const .

3. @Ben Следовательно, почему я сказал «Temp. getArray() = NULL; предотвращено» — как в, я хочу, чтобы это было предотвращено «не [разрешая модификацию] самого массива». Таким образом, указатель const действительно делает что-то полезное.

4. @SSight3: Нет, const указатель не делает ничего полезного. Возврат по значению, а не по ссылке, делает что-то полезное.

Ответ №4:

Самое большее, что вы действительно можете сделать, это вернуть что-то, что не является приемлемым операндом для delete . Это может быть объект RAII (например, std::vector или std::array) или это может быть ссылка (в зависимости от вашей ситуации, любой из них может быть подходящим).

[Un] к счастью, C позволяет программисту делать всевозможные хитрые вещи.

Ответ №5:

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

 charamp; operator[](size_t index) { return Array[index]; }
  

Это не касается возможности обрабатывать его как массив символов, но, как было указано, если вы покажете этот указатель, (плохие) программисты могут свободно запускать delete на нем все, что они хотят.