Почему указателю на массив char нужен strcpy для назначения символов его массиву, а назначение двойных кавычек не будет работать?

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

Debug Assertion Failed Expression: _BLOCK_TYPE_IS_VALID(pHead->nBlockUse) из Visual Studio 2008

//Won't work when deleting pointer:
    char *at = new char [3];
    at = "tw"; //   <-- not sure what going on here that strcpy does differently
    at[2] = '\0'; // <-- causes program to hang
    delete at;

//Works fine when deleting pointer:
    char *at = new char [3];
    strcpy(at,"t");
    at[1] = 'w';
    at[2] = '\0';
    delete at;

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

Ответ 1

Потому что char* не является строкой. Это просто указатель на какой-то символ, с соглашением о том, что может быть больше персонажей, и что после последнего есть '\0'.

Строковый литерал в C (и, следовательно, на С++), такой как "abc", представляет собой всего лишь массив символов, при этом компилятор молча добавляет '\0'. Когда вы назначаете массив указателю, массив молча преобразует указатель в первый элемент. В результате

at = "tw";

означает, что указателю at присваивается адрес первого символа в строковом литерале "tw". Таким образом, он потеряет свою старую ценность. Поскольку это был адрес динамически выделенного массива символов, вы пропускаете этот массив.

Когда вы позже назначаете символ в массиве at теперь указывает на, вы назначаете новое значение некоторому символу в строковом литерале. То, что вызывает поведение undefined, и программа, зависающая или сбой немедленно, вероятно, лучшая, что может случиться с вами, когда вы это сделаете. (На многих платформах, которые вы пишете в память только для чтения).

Позже вы пройдете at до delete[] not delete, так как вы вызвали new[], а не new). При этом вы передаете ему адрес строкового литерала вместо выделенного массива символов. Это, конечно, испортит менеджер кучи. (Библиотека времени выполнения VC ловит это в режиме отладки.)

std::strcpy, с другой стороны, копирует строковый символ по символу из одного массива в другой массив. Никакие указатели не будут изменены, копируются только фрагменты памяти. Указатель на целевой массив по-прежнему указывает на целевой массив впоследствии, только данные в этом массиве изменились.

Позвольте мне добавить это: Как новичок в С++, вы должны использовать std::string, а не строки C. Это делает всю грязную работу для вас и имеет разумную семантику.

Ответ 2

Когда вы делаете

char *at = ...;

at = "hello";

В основном вы переписываете значение указателя (т.е. адрес выделенной вам памяти new[]) с адресом статической константной строки. Это означает, что когда вы позже удалите эту память, вы передаете delete указатель, который ранее не возвращался new.

Это плохо, что нужно делать.

В C и С++ назначения указателям обычно ничего не делают для указателя на память, они меняют сам указатель. Это может сбивать с толку, если вы привыкли к языку, на котором строки являются более "гражданами первого класса".

Кроме того, вы должны использовать delete[], если вы использовали new[].

Ответ 3

Есть три вещи для понимания:

1) char *at; - это просто указательная переменная.
Переменная указателя просто означает, что она содержит адрес памяти.

2) new char[3] возвращает начальный адрес памяти, выделенной в куче.

3) "hello" возвращает адрес строкового литерала.

char *at = new char [3];
//at now contains the address of the memory allocated on the heap


at = "hello";
//at now contains the address of the static string. 
// (and by the way you just created a 3 byte memory leak)


delete[] at; 
//WOOPS!!!! you can't do that because you aren't deleting 
// the original 3 chars anymore which were allocated on the heap!
//Since at contains the string literal memory address you're 
// trying to delete the string literal.

Заметка об изменении памяти только для чтения:

Также вы никогда не должны изменять строковый литерал. То есть это никогда не должно быть сделано:

char *at = "hello";
at[2] = '\0'; 

Память для строковых литералов должна быть только для чтения, и если вы ее измените, результаты будут undefined на языке С++.

Поскольку вы используете С++:

Так как вы используете С++, используйте вместо этого тип std::string.

#include <string>

using namespace std;

int main(int argc, char **argv)
{
  string s = "hello";
  s += " world!";

  //s now contains "hello world!"

  s = "goodbye!";

  //Everything is still valid, and s contains "goodbye!"


  //No need to cleanup s. 

  return 0;
}

Ответ 4

Не забудьте использовать

delete []

всякий раз, когда вы выделяете что-то с помощью [].

Ответ 5

Указатель содержит адрес. Оператор = для указателя изменяет адрес.

at = "tw";

Делает точку в массиве "tw" (массив, созданный компилятором для хранения символов tw), он больше не указывает на массив, который вы создали с новым. созданный в файле.

at[2] = '\0';

Добавляет NULL в конец массива complier.

Ответ 6

В первом примере вы перебираете значение на, во втором вы меняете значение того, что находится в точках. Присвоение строки char * в двойную кавычку присваивает ее статическому указателю const.

В частности, в первом примере теперь указывает другое место в памяти.

Ответ 7

В первом примере вы выделяете некоторую память и указываете на нее с переменной "at". Когда вы делаете

at = "tw"

вы эффективно перенаправляете char * в строку с постоянным символом. Это приведет к утечке памяти. Когда вы продолжаете удалять "at", вы пытаетесь удалить стек стека.

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

Ответ 8

В первом примере вы обнаружили утечку памяти.

Ваша переменная at - это указатель на адрес памяти, а не сама строка. Когда вы назначаете адрес "tw" указателю, вы потеряли исходный адрес, который вы получили с помощью new. at теперь указывает на адрес, который вы не выделили с помощью new, поэтому вы не можете delete его.

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

char *at = new char[3];    // 0x1000
at = "tw";                 // 0x2000
at[2] = '\0';              // set char at 0x2002 to 0
delete at;                 // delete 0x2000 (whoops, didn't allocate that!)

Ответ 9

Вы допускаете две вещи: указание указателя на что-то другое (это то, что присваивание) и копирование некоторых данных в место, указанное указателем.

at = "tw";

этот код делает at указывать на литерал "tw", созданный где-то в постоянной памяти. Попытка написать ему поведение undefined.

char *at = new char [3];
strcpy(at,"t");

этот код выделяет память для трех символов и делает at указывать на эту часть памяти (строка 1), а затем копирует некоторые данные в память, на которые указывает at.

И помните, что память, выделенная с помощью new[], должна быть освобождена с помощью delete[], а не delete

Я советую вам узнать больше о указателях. Это обсуждение охватывает это.