c ошибка указателя

если мы объявим char * p="hello"; то, поскольку он записан в разделе данных, мы не можем изменить содержимое, на которое указывает p, но мы можем изменить сам указатель. но я нашел этот пример в C Traps and Pitfalls Andrew Koenig AT & T Bell Laboratories Мюррей Хилл, Нью-Джерси 07974

пример

 char *p, *q; p = "xyz"; q = p; q[1] = 'Y'; 

q будет указывать на память, содержащую строку xYz. Так будет p, потому что p и q указывают на одну и ту же память.

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

 main() { char *p="hai friends",*p1; p1=p; while(*p!='\0') ++*p++; printf("%s %s",p,p1); } 

и получил выход как ibj!gsjfoet

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

В том же примере возникает segmentation fault в моей системе.

Здесь вы столкнулись с неопределенным поведением. .data (обратите внимание, что строковый литерал может быть в .text тоже) не обязательно неизменен – ​​нет гарантии, что машина будет писать эту память (через таблицы страниц), в зависимости от операционной системы и компилятора.

Только ваша ОС может гарантировать, что материал в разделе данных доступен только для чтения, и даже это связано с установкой пределов сегмента и флагов доступа, а также с использованием указателей и т. Д., Поэтому это не всегда делается.

Сам C не имеет такого ограничения; в модели с плоской памятью (которую используют почти все 32-разрядные ОС), любые байты в вашем адресном пространстве потенциально доступны для записи, даже в разделе кода. Если у вас есть указатель на main () и некоторые знания машинного языка, а также на OS, у которой есть вещи, которые были настроены правильно (точнее, не удалось предотвратить его), вы могли бы переписать его, чтобы просто вернуть 0. Обратите внимание, что это все черная магия рода, и редко делается намеренно, но это часть того, что делает C таким мощным языком для системного программирования.

Даже если вы можете это сделать, и кажется, что ошибок нет, это плохая идея. В зависимости от рассматриваемой программы вы можете в конечном итоге облегчить атаки переполнения буфера. Хорошая статья, объясняющая это:

https://www.securecoding.cert.org/confluence/display/seccode/STR30-C.+Do+not+attempt+to+modify+string+literals

Это зависит от компилятора относительно того, работает оно или нет.

x86 – это архитектура фон Неймана (в отличие от Гарварда ), поэтому нет четкой разницы между памятью «данные» и «программа» на базовом уровне (т. е. компилятор не вынужден иметь разные типы для программной и оперативной памяти, и поэтому не обязательно ограничивает какую-либо переменную той или иной).

Таким образом, один компилятор может разрешить модификацию строки, а другой – нет.

Я предполагаю, что более мягкий компилятор (например, cl, компилятор MS Visual Studio C ++) позволит это, в то время как более строгий компилятор (например, gcc) не будет. Если ваш компилятор позволяет это, скорее всего, это эффективно меняет ваш код на что-то вроде:

 ... char p[] = "hai friends"; char *p1 = p; ... // (some disassembly required to really see what it's done though) 

возможно, с «хорошим намерением» разрешить использование новых кодеров C / C ++ с меньшим ограничением / меньшим количеством запутанных ошибок. (независимо от того, является ли это «хорошей вещью», до тех пор, пока я не буду обсуждать, и я буду придерживаться своего мнения в основном из этой должности: P)

Из интереса, какой компилятор вы использовали?

В старые времена, когда C, описанный K & R в их книге «Язык программирования C», был «стандартным», то, что вы описали, было совершенно нормально. Фактически, некоторые компиляторы перепрыгнули через обручи, чтобы сделать литералы на строках доступными для записи. Они с трудом скопировали строки из текстового сегмента в сегмент данных при инициализации.

Даже сейчас gcc имеет флаг для восстановления этого поведения: -fwritable-strings .

 main() { int i = 0; char *p= "hai friends", *p1; p1 = p; while(*(p + i) != '\0') { *(p + i); i++; } printf("%s %s", p, p1); return 0; } 

Этот код даст результат: hai friends hai friends

Изменение строковых литералов – плохая идея, но это не значит, что это может не сработать.

Одна из причин, почему это не так: вашему компилятору разрешено брать несколько экземпляров одного и того же строкового литерала и указывать на один и тот же блок памяти. Поэтому, если «xyz» был определен где-то еще в вашем коде, вы можете непреднамеренно сломать другой код, который ожидал, что он будет постоянным.

Ваша программа также работает в моей системе (Windows + cygwin). Однако в стандарте говорится, что вы не должны этого делать, хотя следствие не определено.

Следуя выдержке из книги C: Справочное руководство 5 / E, стр. 33,

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

 char p1[] = "Always writable"; char *p2 = "Possibly not writable"; const char p3[] = "Never writable"; 

p1 всегда будет работать; p2 может работать или может привести к ошибке во время выполнения ; p3 всегда вызывает ошибку времени компиляции.

Хотя модификация строкового литерала может быть возможна в вашей системе, это причуда вашей платформы, а не гарантия языка. Фактический язык C ничего не знает о разделах .data или .text. Это все детали реализации.

На некоторых встроенных системах у вас даже не будет файловой системы для хранения файла с секцией .text. На некоторых таких системах ваши строковые литералы будут храниться в ПЗУ, и попытка записи на ПЗУ приведет к простому сбою устройства.

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

p эффективно указывает на память только для чтения. Результат присвоения массиву p указывает на, вероятно, неопределенное поведение. Просто потому, что компилятор позволяет вам уйти с ним, это не значит, что все в порядке.

Взгляните на этот вопрос из C-FAQ: comp.lang.c FAQ список · Вопрос 1.32

В: В чем разница между этими инициализациями?

 char a[] = "string literal"; char *p = "string literal"; 

Моя программа сработает, если я попытаюсь присвоить новое значение p [i].

A: Строковый литерал (формальный термин для строки с двумя кавычками в источнике C) можно использовать двумя разными способами:

  1. В качестве инициализатора для массива char, как и в объявлении char a [], он задает начальные значения символов в этом массиве (и, при необходимости, его размер).
  2. В другом месте он превращается в неназванный, статический массив символов, и этот неназванный массив может храниться в постоянной памяти и поэтому не может быть изменен. В контексте выражения массив преобразуется сразу в указатель, как обычно (см. Раздел 6), поэтому второе объявление инициализирует p, чтобы указать на первый элемент неименованного массива.

Некоторые компиляторы имеют переключатель, контролирующий, являются ли строковые литералы доступными для записи или нет (для компиляции старого кода), а некоторые могут иметь параметры, чтобы заставить строковые литералы формально обрабатываться как массивы const char (для лучшего обнаружения ошибок).

Я думаю, что вы делаете большую путаницу в очень важной общей концепции, чтобы понять, когда используете C, C ++ или другие языки низкого уровня. На низкоуровневом языке существует неявное предположение, чем программист знает, что он делает, и не делает ошибки программирования .

Это предположение позволяет разработчикам языка просто игнорировать то, что должно произойти, если программист нарушает правила. Конечный эффект заключается в том, что в C или C ++ нет гарантии «времени выполнения» … если вы делаете что-то плохое, просто это НЕ ОПРЕДЕЛЕННО («неопределенное поведение» – это законный термин), что должно произойти. Может быть, авария (если вам очень повезло), или может быть просто явно ничего (к сожалению, в большинстве случаев … с может быть сбой в совершенно правильном месте, когда миллион выполненных инструкций позже).

Например, если вы заходите за пределы массива, МОЖЕТ БЫТЬ, вы получите крах, может быть, нет, может даже быть демон, который выйдет из вашего носа (это «носовой демон», который вы можете найти в Интернете). Просто не то, что написал компилятор, подумал.

Просто никогда не делайте этого (если вам нравится писать приличные программы).

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