проблема с указателем на символ - PullRequest
4 голосов
/ 22 июня 2010

если мы объявляем char * p="hello";, то, поскольку он записан в разделе данных, мы не можем изменять содержимое, на которое указывает p, но мы можем изменить сам указатель.но я нашел этот пример в C Traps and Pitfalls Andrew Koenig AT & T Bell Laboratories Murray Hill, Нью-Джерси 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

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

Ответы [ 11 ]

5 голосов
/ 22 июня 2010

Ваш же пример вызывает ошибку сегментации в моей системе.

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

4 голосов
/ 22 июня 2010

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

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

3 голосов
/ 22 июня 2010

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

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

1 голос
/ 11 марта 2015
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;
}

Этот код выдаст: хай друзья хай друзья

1 голос
/ 22 июня 2010

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

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

1 голос
/ 22 июня 2010

Зависит от компилятора, будет ли он работать или нет.

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)

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

0 голосов
/ 22 июня 2010

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

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

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

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

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

0 голосов
/ 22 июня 2010

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

Посмотрите на этот вопрос из C-FAQ: comp.lang.c Список часто задаваемых вопросов · Вопрос 1.32

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

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

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

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

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

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

0 голосов
/ 22 июня 2010

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

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

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

0 голосов
/ 22 июня 2010

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...