Почему указатель на массив символов должен нуждаться в 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's 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? Оба они отлично обработают строку, и отладчик не покажет ничего другого.


person Omar    schedule 16.10.2009    source источник
comment
Я полагаю, вы имели в виду delete[]   -  person MSalters    schedule 16.10.2009
comment
Вы сами ответили на свой вопрос. strcpy() присваивает символы массиву. = присваивает новый массив. Отладчик действительно показывает что-то другое. Значение 'at' меняется в одном случае, а не в другом.   -  person user207421    schedule 03.07.2014
comment
К вашему сведению, выполнение at = "tw";, а затем at[2] = '\0'; является излишним. "tw" создает строковый литерал, который уже завершается нулем. Память для строки "tw" выглядит как [ 't' | 'w' | '\0' ]. Не только это, но и at[2] = '\0'; приведет к неопределенному поведению; "tw" создает строковый литерал, который является строкой только для чтения, которая недоступна для записи, поэтому запись в этот строковый литерал только для чтения вызовет неопределенное поведение. Чтобы на самом деле назначить что-то таким образом, вам нужно будет сделать const char *at = "tw";, который создаст строковый литерал, а at укажет на то же место.   -  person RastaJedi    schedule 18.04.2016


Ответы (9)


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

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

at = "tw";

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

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

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

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

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

person sbi    schedule 16.10.2009

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

char *at = ...;

at = "hello";

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

Это плохо.

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

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

person unwind    schedule 16.10.2009
comment
Так что я был бы прав, если предположить, что strcpy(var,string) перебирает каждый отдельный символ в строке и назначает его правильному индексу в var? - person Omar; 16.10.2009
comment
@Omar: Да, strcpy() будет записывать по одному символу за раз, включая завершающий символ NIL. - person unwind; 19.10.2009

Есть три вещи, которые нужно понять:

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's memory address you're 
// trying to delete the string literal.

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

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

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

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

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

Поскольку вы используете C++, рассмотрите возможность использования вместо этого типа 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;
}
person Brian R. Bondy    schedule 16.10.2009

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

delete []

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

person alexkr    schedule 16.10.2009

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

at = "tw";

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

at[2] = '\0';

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

person Patrick    schedule 16.10.2009
comment
at[2] = '\0'; Добавляет NULL в конец массива компилятора — это вызывает UB, поскольку строковые литералы (массив, созданный компилятором, на который вы ссылаетесь) доступны только для чтения и никогда не должны изменяться. Но я предполагаю, что вы это знали; Я просто хотел указать на это другим, читающим ваш ответ. - person RastaJedi; 18.04.2016

В первом примере вы меняете значение at, во втором — значение того, на что указывает at. Присвоение char * строке с двойными кавычками присваивает ей статический константный указатель.

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

person pythonic metaphor    schedule 16.10.2009
comment
В примере const char *s = "hello world; s является указателем на const char, а не const pointer на char. В вашем ответе так много опечаток ... Кроме того, я предполагаю, что вы, возможно, имели в виду присвоить строку с двойными кавычками char *, а не наоборот? Поскольку вы ничего не можете присвоить строковому литералу, то есть строковый литерал никогда не может быть в LHS? - person RastaJedi; 18.04.2016

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

at = "tw"

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

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

person resolveaswontfix    schedule 16.10.2009

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

Ваша переменная 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!)
person KingPong    schedule 16.10.2009

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

at = "tw";

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

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

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

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

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

person Tadeusz Kopec    schedule 16.10.2009