#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 на нем все, что они хотят.