Fixed c что это

C++ (Cpp) Fixed — 30 примеров найдено. Это лучшие примеры C++ (Cpp) кода для Fixed, полученные из open source проектов. Вы можете ставить оценку каждому примеру, чтобы помочь нам улучшить качество примеров.

Основные методы

Основные методы

Основные методы

GTEST_TEST(Fixed, version)

Top 8 Linux Commands Every Linux Us.

Источник: cpp.hotexamples.com

Какая польза от ключевого слова С# исправлено/небезопасно? [дубликат]

Вам нужно исправить это, чтобы указатель, который вы используете, оставался действительным. Но, честно говоря, случаи использования небезопасного кода относительно редки. Я много лет пишу на C# и до сих пор даже не помню различных правил небезопасного кодирования. — Jon Skeet

Проверь это: msdn.microsoft.com/en-us/library/f58wzh21.aspx — w.b

Отсутствие ответа объясняет это утверждение, найденное в связанном примере: «Существует небольшая стоимость использования фиксированного оператора. Так это только поможет на операции, которые тратят значительное количество времени в небезопасном коде», что, по крайней мере, неоднозначно, возможно, неправильно, в зависимости от того, как вы читаете первую часть. Неясно, корректируются ли CLR «нефиксированные» указатели, когда GC перемещает выделенный блок памяти (за счет некоторой потери времени) , или нет, и начинают указывать не на то место (поэтому ключевое слово ‘fixed’ не необязательный но обязательно с указателями — это мое понимание). — mins

Mixed Messages (Official video)

3 ответы

C # — это управляемый язык это означает, что память управляется автоматически, т.е. не вами. Если вы не использовали fixed к тому времени, когда вы приступите к изменению памяти, на которую указывает ваш указатель, C# мог бы переместить переменную в другую ячейку памяти, поэтому вы могли бы изменить что-то еще! fixed логически фиксирует переменную в памяти, чтобы она не перемещалась.

Почему С# перемещает переменные в памяти? Чтобы сжать память, в противном случае программы будут использовать больше доступной им памяти, если объекты, которые больше не существуют, оставляют дыры, в которые не могут поместиться другие объекты (фрагментация кучи памяти). я использовал fixed широко в .NET-библиотеке разработан для устройств с ограниченными ресурсами, чтобы избежать копирования мусора в буферы, и найти эту функцию, которой очень не хватает в других управляемых языках, где вы не можете сделать то же самое. При написании игр на управляемом языке сборка мусора часто является одним из самых больших узких мест, поэтому возможность не создавать ее очень полезна! Смотрите мой вопрос здесь: С# Копировать переменные в буфер, не создавая мусора? по одной причине.

ответ дан 26 мар ’23, в 00:03

«Я широко использовал fix в библиотеке .NET, предназначенной для устройств с ограниченными ресурсами, чтобы избежать копирования мусора в буферы, и обнаружил, что этой функции очень не хватает в других управляемых языках, где вы не можете сделать то же самое. При написании игр на управляемом языке сборка мусора часто одно из самых больших узких мест, поэтому возможность не создавать его очень полезна!» —> Спасибо, но извините, не могли бы вы уточнить. Кажется, я понял, что на самом деле закрепление памяти предназначено для производительности. Но я на самом деле. не понимаю, как, был бы признателен, если бы вы уточнили это! — Мазок. Джат

C/C++ и C# что это такое?

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

Спасибо, Марк. Прочтите это. Интересно, какой прирост производительности вы получили, не используя промежуточный байт [] и вместо этого копируя через закрепленную область памяти? Было ли это значительным? — Мазок. Джат

Если вы находитесь на телефоне или устройстве, таком как Ouya (запускает моно) и много копируете, это было заметно — приложение зависает, когда должен работать GC — теперь этого не происходит, потому что мусора нет. — маркмнл

Большое спасибо за ваш вклад. — Мазок. Джат

  • он позволяет закрепить массив и получить указатель на данные
  • при использовании в unsafe struct поле, оно объявляет «фиксированный буфер» — зарезервированный блок пространства в типе, доступ к которому осуществляется с помощью указателей, а не обычных полей.

Чтобы ответить на конкретном примере — вот некоторый код, который используется для выполнения семантического равенства между двумя byte[] произвольного размера.

internal static unsafe int GetHashCode(byte[] value) < unchecked < if (value == null) return -1; int len = value.Length; if (len == 0) return 0; int octects = len / 8, spare = len % 8; int acc = 728271210; fixed (byte* ptr8 = value) < long* ptr64 = (long*)ptr8; for (int i = 0; i < octects; i++) < long val = ptr64[i]; int valHash = (((int)val) ^ ((int)(val >> 32))); acc = (((acc int offset = len — spare; while(spare— != 0) < acc = (((acc > return acc; > >

Итак, если, например, в буфере было 1000 элементов, рассматривая его как набор long теперь мы делаем только 125 итераций, а не просматриваем все 1000 по отдельности, плюс мы полностью обходим любую проверку границ массива (которую JIT может или не может удалить, в зависимости от того, как Очевидный похоже, что вы не можете их нарушать).

Спасибо, Марк — «например, в буфере было 1000 элементов, рассматривая его как набор длинных, мы теперь делаем только 125 итераций, а не просматриваем все 1000 по отдельности» — не уверен, что я следую за вами, но я предполагаю вы говорите, что это в основном «Соблюдение производительности». Если у вас есть большой граф объектов — иногда будет быстрее, если вы зафиксируете/фиксируете его расположение в памяти во время итерации по нему? Если это так, то это единственное «практическое» использование, которое я слышал до сих пор (все остальные просто повторяют «да, исправьте это в памяти») — Мазок. Джат

привет Марк, спасибо. Марк привел мне очень полезный пример — stackoverflow.com/questions/15307431/… — Мазок. Джат

Unsafe кодовые блоки используются довольно редко. Они преимущественно используются, если вы хотите создавать и использовать указатели. Кроме того, CLR не может контролировать любой код в небезопасном блоке. Это считается неподдающимся проверке (из MSDN) в отношении CLR.

Теперь, когда происходит сборка мусора, какой-то объект вполне может быть перемещен. При префиксе fixed мы говорим платформе не перемещать объекты, на которые указывают указатели адресов.

Обратите внимание, что «Исправлено» — это проблема взаимодействия. КОГДА вы получаете указатель и передаете его неуправляемому коду (например, собственной dll для сброса туда некоторых данных), вы НЕ МОЖЕТЕ, чтобы сборщик мусора перемещал этот объект во время выполнения вашего вызова. Следовательно, фиксируя его на месте. — TomTom

Вот что говорит MSDN — исправьте расположение памяти, да, я понял. Но почему? Любое практическое использование, зачем нам вообще нужна эта языковая функция? Спасибо — Мазок. Джат

В основном при выполнении взаимодействия и маршалинга. Если вы хотите сохранить указатели на более длительное время. — датский язык

Не тот ответ, который вы ищете? Просмотрите другие вопросы с метками c# or задайте свой вопрос.

Источник: stackovergo.com

Указатели C++

Указатели, без сомнения, — один из самых важных и сложных аспектов C++. В значительной степени мощь многих средств C++ определяется использованием указателей. Например, благодаря им обеспечивается поддержка связных списков и динамического выделения памяти, и именно они позволяют функциям изменять содержимое своих аргументов. Однако об этом мы поговорим в последующих главах, а пока (т.е. в этой главе) мы рассмотрим основы применения указателей и покажем, как можно избежать некоторых потенциальных проблем, связанных с их использованием.

При рассмотрении темы указателей нам придется использовать такие понятия, как размер базовых С++-типов данных. В этой главе мы предположим, что символы занимают в памяти один байт, целочисленные значения — четыре, значения с плавающей точкой типа float — четыре, а значения с плавающей точкой типа double — восемь (эти размеры характерны для типичной 32-разрядной среды).
Что представляют собой указатели
Указатели — это переменные, которые хранят адреса памяти. Чаще всего эти адреса обозначают местоположение в памяти других переменных. Например, если х содержит адрес переменной у, то о переменной, х говорят, что она «указывает» на у.
Указатель — это переменная, которая содержит адрес другой переменной.
Переменные-указатели (или переменные типа указатель) должны быть соответственно объявлены. Формат объявления переменной-указателя таков:

тип *имя_переменной;
Здесь элемент тип означает базовый тип указателя, причем он должен быть допустимым С++-типом. Элемент имя_переменной представляет собой имя переменной-указателя. Рассмотрим пример. Чтобы объявить переменную р указателем на int-значение, используйте следующую инструкцию.

int *р;
Для объявления указателя на float-значение используйте такую инструкцию.

float *р;
В общем случае использование символа «звездочка» (*) перед именем переменной в инструкции объявления превращает эту переменную в указатель.
Базовый тип указателя определяет тип данных, на которые он будет ссылаться.
Тип данных, на которые будет ссылаться указатель, определяется его базовым типом. Рассмотрим еще один пример.

int *ip; // указатель на целочисленное значение

double *dp; // указатель на значение типа double
Как отмечено в комментариях, переменная ip — это указатель на int-значение, поскольку его базовым типом является тип int, а переменная dp — указатель на double-значение, поскольку его базовым типом является тип double, Следовательно, в предыдущих примерах переменную ip можно использовать для указания на int-значения, а переменную dp на double-значения. Однако помните: не существует реального средства, которое могло бы помешать указателю ссылаться на «бог-знает-что». Вот потому-то указатели потенциально опасны.
Операторы, используемые с указателями
С указателями используются два оператора: «*» и «» — унарный. Он возвращает адрес памяти, по которому расположен его операнд. (Вспомните: унарному оператору требуется только один операнд.) Например, при выполнении следующего фрагмента кода

balptr = &balance;
в переменную balptr помещается адрес переменной balance. Этот адрес соответствует области во внутренней памяти компьютера, которая принадлежит переменной balance. Выполнение этой инструкции никак не повлияло на значение переменной balance. Назначение оператора можно «перевести» на русский язык как «адрес переменной», перед которой он стоит.

Следовательно, приведенную выше инструкцию присваивания можно выразить так: «переменная balptr получает адрес переменной balance». Чтобы лучше понять суть этого присваивания, предположим, что переменная balance расположена в области памяти с адресом 100. Следовательно, после выполнения этой инструкции переменная balptr получит значение 100.
Второй оператор работы с указателями (*) служит дополнением к первому (
переменной value будет присвоено значение переменной balance, на которую указывает переменная balptr. Например, если переменная balance содержит значение 3200, после выполнения последней инструкции переменная value будет содержать значение 3200, поскольку это как раз то значение, которое хранится по адресу 100. Назначение оператора «*» можно выразить словосочетаинем «по адресу».

В данном случае предыдущую инструкцию можно прочитать так: «переменная value получает значение (расположенное) по адресу balptr». Действие приведенных выше двух инструкций схематично показано на рис. 6.1.
Последовательность операций, отображенных на рис. 6.1, реализуется в следующей программе.

using namespace std;

>
При выполнении этой программы получаем такие результаты:

Баланс равен: 3200
К сожалению, знак умножения (*) и оператор со значением «по адресу» обозначаются одинаковыми символами «звездочка», что иногда сбивает с толку новичков в языке C++. Эти операции никак не связаны одна с другой. Имейте в виду, что операторы «*» и «f; // ОШИБКА!
Некорректность этого фрагмента состоит в недопустимости присваивания double-указателя int-указателю. Выражение f; // Теперь формально все ОК!
Операция приведения к типу (int *) вызовет преобразование double- к int-указателю. Все же использование операции приведения в таких целях несколько сомнительно, поскольку именно базовый тип указателя определяет, как компилятор будет обращаться с данными, на которые он ссылается. В данном случае, несмотря на то, что p (после выполнения последней инструкции) в действительности указывает на значение с плавающей точкой, компилятор по-прежнему «считает», что он указывает на целочисленное значение (поскольку р по определению — int-указатель).
Чтобы лучше понять, почему использование операции приведения типов при присваивании одного указателя другому не всегда приемлемо, рассмотрим следующую программу.

// Эта программа не будет выполняться правильно.

using namespace std;

p = (int *) // Используем операцию приведения типов для присваивания double-указателя int-указателю.

у = *р; // Что происходит при выполнении этой инструкции?

>
Как видите, в этой программе переменной p (точнее, указателю на целочисленное значение) присваивается адрес переменной х (которая имеет тип double). Следовательно, когда переменной y присваивается значение, адресуемое указателем р, переменная y получает только четыре байт данных (а не все восемь, требуемые для double-значения), поскольку р— указатель на целочисленный тип int. Таким образом, при выполнении cout-инструкции на экран будет выведено не число 123.23, а, как говорят программисты, «мусор». (Выполните программу и убедитесь в этом сами.)
Присваивание значений с помощью указателей
При присваивании значения области памяти, адресуемой указателем, его (указатель) можно использовать с левой стороны от оператора присваивания. Например, при выполнении следующей инструкции (если р — указатель на целочисленный тип)

*р = 101;
число 101 присваивается области памяти, адресуемой указателем р. Таким образом, эту инструкцию можно прочитать так: «по адресу р помещаем значение 101». Чтобы инкрементировать или декрементировать значение, расположенное в области памяти, адресуемой указателем, можно использовать инструкцию, подобную следующей.

(*р)++;
Круглые скобки здесь обязательны, поскольку оператор «*» имеет более низкий приоритет, чем оператор «++».
Присваивание значений с использованием указателей демонстрируется в следующей программе.

using namespace std;

>
Вот такие результаты генерирует эта программа.

100 101 100
Использование указателей в выражениях
Указатели можно использовать в большинстве допустимых в C++ выражений. Но при этом следует применять некоторые специальные правила и не забывать, что некоторые части таких выражений необходимо заключать в круглые скобки, чтобы гарантированно получить желаемый результат.
Арифметические операции над указателями
С указателями можно использовать только четыре арифметических оператора: ++, — , + и — . Чтобы лучше понять, что происходит при выполнении арифметических действий с указателями, начнем с примера. Пусть p1 — указатель на int-переменную с текущим значением 2 ООО (т.е. p1 содержит адрес 2 ООО). После выполнения (в 32-разрядной среде) выражения

p1++;
содержимое переменной-указателя p1 станет равным 2 004, а не 2 001! Дело в том, что при каждом инкрементировании указатель p1 будет указывать на следующее int-значение. Для операции декрементирования справедливо обратное утверждение, т.е. при каждом декрементировании значение p1 будет уменьшаться на 4. Например, после выполнения инструкции

p1—;
указатель p1 будет иметь значение 1 996, если до этого оно было равно 2 000. Итак, каждый раз, когда указатель инкрементируется, он будет указывать на область памяти, содержащую следующий элемент базового типа этого указателя. А при каждом декрементировании он будет указывать на область памяти, содержащую предыдущий элемент базового типа этого указателя.
Для указателей на символьные значения результат операций инкрементирования и декрементирования будет таким же, как при «нормальной» арифметике, поскольку символы занимают только один байт. Но при использовании любого другого типа указателя при инкрементировании или декрементировании значение переменной-указателя будет увеличиваться или уменьшаться на величину, равную размеру его базового типа.
Арифметические операции над указателями не ограничиваются использованием операторов инкремента и декремента. Со значениями указателей можно выполнять операции сложения и вычитания, используя в качестве второго операнда целочисленные значения. Выражение

p1 = p1 + 9;
заставляет p1 ссылаться на девятый элемент базового типа указателя p1 относительно элемента, на который p1 ссылался до выполнения этой инструкций.
Несмотря на то что складывать указатели нельзя, один указатель можно вычесть из другого (если они оба имеют один и тот же базовый тип). Разность покажет количество элементов базового типа, которые разделяют эти два указателя.
Помимо сложения указателя с целочисленным значением и вычитания его из указателя, а также вычисления разности двух указателей, над указателями никакие другие арифметические операции не выполняются. Например, с указателями нельзя складывать float- или double-значения.
Чтобы понять, как формируется результат выполнения арифметических операций над указателями, выполним следующую короткую программу. Она выводит реальные физические адреса, которые содержат указатель на int-значение (i) и указатель на float-значение (f). Обратите внимание на каждое изменение адреса (зависящее от базового типа указателя), которое происходит при повторении цикла. (Для большинства 32-разрядных компиляторов значение i будет увеличиваться на 4, а значение f — на 8.) Отметьте также, что при использовании указателя в cout-инструкции его адрес автоматически отображается в формате адресации, применяемом для текущего процессора и среды выполнения.

// Демонстрация арифметических операций над указателями.

using namespace std;

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

0012FE80 0012FECC
Узелок на память. Все арифметические операции над указателями выполняются относительно базового типа указателя.
Сравнение указателей
Указатели можно сравнивать, используя операторы отношения ==, < и >. Однако для того, чтобы результат сравнения указателей поддавался интерпретации, сравниваемые указатели должны быть каким-то образом связаны. Например, если p1 и р2 — указатели, которые ссылаются на две отдельные и никак не связанные переменные, то любое сравнение p1 и р2 в общем случае не имеет смысла. Но если p1 и р2 указывают на переменные, между которыми существует некоторая связь (как, например, между элементами одного и того же массива), то результат сравнения указателей p1 и р2 может иметь определенный смысл. Ниже в этой главе мы рассмотрим пример программы, в которой используется сравнение указателей.
Указатели и массивы
В C++ указатели и массивы тесно связаны между собой, причем настолько, что зачастую понятия «указатель» и «массив» взаимозаменяемы. В этом разделе мы попробуем проследить эту связь. Для начала рассмотрим следующий фрагмент программы.

p1 = str;
Здесь str представляет собой имя массива, содержащего 80 символов, a p1 — указатель на тип char. Особый интерес представляет третья строка, при выполнении которой переменной p1 присваивается адрес первого элемента массива str. (Другими словами, после этого присваивания p1 будет указывать на элемент str[0].) Дело в том, что в C++ использование имени массива без индекса генерирует указатель на первый элемент этого массива. Таким образом, при выполнении присваивания p1 = str адрес stг[0] присваивается указателю p1. Это и есть ключевой момент, который необходимо четко понимать: неиндексированное имя массива, использованное в выражении, означает указатель на начало этого массива.
Имя массива без индекса образует указатель на начало этого массива.
Поскольку после рассмотренного выше присваивания p1 будет указывать на начало массива str, указатель p1 можно использовать для доступа к элементам этого массива. Например, если нужно получить доступ к пятому элементу массива str, используйте одно из следующих выражений:

*(p1+4)
В обоих случаях будет выполнено обращение к пятому элементу. Помните, что индексирование массива начинается с нуля, поэтому при индексе, равном четырем, обеспечивается доступ к пятому элементу. Точно такой же эффект производит суммирование значения исходного указателя (p1) с числом 4, поскольку p1 указывает на первый элемент массива str.
Необходимость использования круглых скобок, в которые заключено выражение p1+4, обусловлена тем, что оператор «*» имеет более высокий приоритет, чем оператор «+». Без этих круглых скобок выражение бы свелось к получению значения, адресуемого указателем p1, т.е. значения первого элемента массива, которое затем было бы увеличено на 4.
Важно! Убедитесь лишний раз в правильности использования круглых скобок в выражении с указателями. В противном случае ошибку будет трудно отыскать, поскольку внешне программа может выглядеть вполне корректной. Если у вас есть сомнения в необходимости их использования, примите решение в их пользу — вреда от этого не будет.
В действительности в C++ предусмотрено два способа доступа к элементам массивов: с помощью индексирования массивов и арифметики указателей. Дело в том, что арифметические операции над указателями иногда выполняются быстрее, чем индексирование массивов, особенно при доступе к элементам, расположение которых отличается строгой упорядоченностью. Поскольку быстродействие часто является определяющим фактором при выборе тех или иных решений в программировании, то использование указателей для доступа к элементам массива— характерная особенность многих С++-программ. Кроме того, иногда указатели позволяют написать более компактный код по сравнению с использованием индексирования массивов.
Чтобы лучше понять различие между использованием индексирования массивов и арифметических операций над указателями, рассмотрим две версии одной и той же программы. В этой программе из строки текста выделяются слова, разделенные пробелами. Например, из строки «Привет дружище» программа должна выделить слова «Привет» и «дружище».

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

тест.
Вот как выглядит версия программы разбиения строки на слова с использованием арифметики указателей.

// Программа разбиения строки на слова:

// версия с использованием указателей.

using namespace std;

// Считываем лексему из строки.

q = token; // Устанавливаем q для указания на начало массива token.

/* Считываем символы до тех пор, пока не встретится либо пробел, либо нулевой символ (признак завершения строки). */

if (*p) р++; // Перемещаемся за пробел.

*q = ‘’; // Завершаем лексему нулевым символом.

>
А вот как выглядит версия той же программы с использованием индексирования массивов.

// Программа разбиения строки на слова:

// версия с использованием индексирования массивов.

using namespace std;

// Считываем лексему из строки.

/* Считываем символы до тех пор пока не встретится либо пробел, либо нулевой символ (признак завершения строки). */

token[j] = ‘’; // Завершаем лексему нулевым символом.

>
У этих программ может быть различное быстродействие, что обусловлено особенностями генерирования кода С++-компиляторами. Как правило, при использовании индексирования массивов генерируется более длинный код (с большим количеством машинных команд), чем при выполнении арифметических действий над указателями. Поэтому неудивительно, что в профессионально написанном С++-коде чаще встречаются версии, ориентированные на обработку указателей. Но если вы— начинающий программист, смело используйте индексирование массивов, пока не научитесь свободно обращаться с указателями.
Индексирование указателя
Как было показано выше, можно получить доступ к массиву, используя арифметические действия над указателями. Интересно то, что в C++ указатель, Который ссылается на массив, можно индексировать так, как если бы это было имя массива (это говорит о тесной связи между указателями и массивами). Соответствующий такому подходу синтаксис обеспечивает альтернативу арифметическим операциям над указателями, поскольку он более удобен в некоторых ситуациях. Рассмотрим пример.

// Индексирование указателя подобно массиву.

using namespace std;

char str[20] = «I love you»;

for (i=0; p[i]; i++) p[i] = toupper (p[i]);

>
При выполнении программа отобразит на экране следующее.

I LOVE YOU
Вот как работает эта программа. Сначала в массив str вводится строка «I love you». Затем адрес начала этой строки присваивается указателю р. После этого каждый символ строки str с помощью функции toupper () преобразуется в его прописной эквивалент посредством индексирования указателя р. Помните, что выражение р[i] по своему действию идентично выражению *(p+i).
О взаимозаменяемости указателей и массивов
Выше было показано, что указатели и массивы очень тесно связаны. И действительно, во многих случаях они взаимозаменяемы. Например, с помощью указателя, который содержит адрес начала массива, можно получить доступ к элементам этого массива либо посредством арифметических действий над указателем, либо посредством индексирования массива. Однако в общем случае указатели и массивы не являются взаимозаменяемыми. Рассмотрим, например, такой фрагмент кода.

*num = i; // Здесь все в порядке.

num++; // ОШИБКА — переменную num модифицировать нельзя.

>
Здесь используется массив целочисленных значений с именем num. Как отмечено в комментарии, несмотря на то, что совершенно приемлемо применить к имени num оператор «*» (который обычно применяется к указателям), абсолютно недопустимо модифицировать значение num. Дело в том, что num — это константа, которая указывает на начало массива. И ее, следовательно, инкрементировать никак нельзя. Другими словами, несмотря на то, что имя массива (без индекса) действительно генерирует указатель на начало массива, его значение изменению не подлежит.
Хотя имя массива генерирует константу-указатель, на него, тем не менее, (подобно указателям) можно включать в выражения, если, конечно, оно при этом не модифицируется. Например следующая инструкция, при выполнении которой элементу num[3] присваивается значение 100, вполне допустима.

*(num+3) = 100; // Здесь все в порядке, поскольку num не изменяется.
Указатели и строковые литералы
Возможно, вас удивит способ обработки С++-компиляторами строковых литералов, подобных следующему.

cout Если С++-компилятор обнаруживает строковый литерал, он сохраняет его в таблице строк программы и генерирует указатель на нужную строку. Поэтому следующая программа совершенно корректна и при выполнении выводит на экран фразу: Работа с указателями — сплошное удовольствие!.

using namespace std;

s = «Работа с указателями — сплошное удовольствие!n»;

>
При выполнении этой программы символы, образующие строковый литерал, сохраняются в таблице строк, а переменной s присваивается указатель на соответствующую строку в этой таблице.
Таблица строк — это таблица, сгенерированная компилятором для хранения строк, используемых в программе.
Поскольку указатель на таблицу строк конкретной программы при использовании строкового литерала генерируется автоматически, то можно попытаться использовать этот факт для модификации содержимого данной таблицы. Однако такое решения вряд ли можно назвать удачным.

Дело в том, что С++-компиляторы создают оптимизированные таблицы, в которых один строковый литерал может использоваться в двух (или больше) различных местах программы. Поэтому «насильственное» изменение строки может вызвать нежелательные побочные эффекты. Более того, строковые литералы представляют собой константы, и некоторые современные С++-компиляторы попросту не позволят менять их содержимое. А при попытке сделать это будет сгенерирована ошибка времени выполнения.
Все познается в сравнении
Выше отмечалось, что значение одного указателя можно сравнивать с другим. Но, чтобы сравнение указателей имело смысл, сравниваемые указатели должны быть каким-то образом связаны друг с другом. Чаще всего такая связь устанавливается в случае, когда оба указателя указывают на элементы одного и того же массива. Например, даны два указателя (с именами А и В), которые ссылаются на один и тот же массив. Если А меньше В, значит, указатель А указывает на элемент, индекс которого меньше индекса элемента, адресуемого указателем В. Такое сравнение особенно полезно для определения граничных условий.
Сравнение указателей демонстрируется в следующей программе. В этой программе создается две переменных типа указатель. Одна (с именем start) первоначально указывает на начало массива, а вторая (с именем end) — на его конец. По мере ввода пользователем чисел массив последовательно заполняется от начала к концу.

Каждый раз, когда в массив вводится очередное число, указатель start инкрементируется. Чтобы определить, заполнился ли массив, в программе просто сравниваются значения указателей start и end. Когда start превысит end, массив будет заполнен «до отказа». Программе останется лишь вывести содержимое заполненного массива на экран.

// Пример сравнения указателей.

using namespace std;

>
Как показано в этой программе, поскольку start и end оба указывают на общий объект (в данном случае им является массив num), их сравнение может иметь смысл. Подобное сравнение часто используется в профессионально написанном С++-коде.
Массивы указателей
Указатели, подобно данным других типов, могут храниться в массивах. Вот, например, как выглядит объявление 10-элементного массива указателей на int-значения.

int *ipa[10];
Здесь каждый элемент массива ipa содержит указатель на целочисленное значение.
Чтобы присвоить адрес int-переменной с именем var третьему элементу этого массива указателей, запишите следующее.

ipa[2] = &var;
Помните, что здесь ipa — массив указателей на целочисленные значения. Элементы этого массива могут содержать только значения, которые представляют собой адреса переменных целочисленного типа. Вот поэтому переменная var предваряется оператором Чтобы присвоить значение переменной var целочисленной переменной х с помощью массива ipa, используйте такой синтаксис.

x = *ipa[2];
Поскольку адрес переменной var хранится в элементе ipa[2], применение оператора „*“ к этой индексированной переменной позволит получить значение переменной var.
Подобно другим массивам, массивы указателей можно инициализировать. Как правило, инициализированные массивы указателей используются для хранения указателей на строки. Например, чтобы создать функцию, которая выводит счастливые предсказания, можно следующим образом определить массив fortunes,

„Вскоре деньги потекут к Вам рекой.n“,

„Вашу жизнь озарит новая любовь.n“,

„Вы будете жить долго и счастливо.n“,

„Деньги, вложенные сейчас в дело, принесут доход.n“,

„Близкий друг будет искать Вашего расположения.n“

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

cout Ниже программа предсказаний приведена целиком. Для получения случайных чисел используется функция rand (), а для получения случайных чисел в диапазоне от 0 до 4 — оператор деления по модулю, поскольку именно такие числа могут служить для доступа к элементам массива по индексу.

using namespace std;

„Вскоре деньги потекут к Вам рекой.n“,

„Вашу жизнь озарит новая любовь.n“,

„Вы будете жить долго и счастливо.n“,

„Деньги, вложенные сейчас в дело, принесут доход.n“,

„Близкий друг будет искать Вашего расположения.n“

// Рандомизируем генератор случайных чисел.

while (!kbhit ()) rand ();

chance = chance % 5;

>
Обратите внимание на цикл while, который вызывает функцию rand () до тех пор, пока не будет нажата какая-либо клавиша. Поскольку функция rand () всегда генерирует одну и ту же последовательность случайных чисел, важно иметь возможность программно использовать эту последовательность с некоторой произвольной позиции. (В противном случае каждый раз после запуска программа будет выдавать одно и то же «предсказание».) Эффект случайности достигается за счет повторяющихся обращений к функции rand (). Когда пользователь нажмет клавишу, цикл остановится на некоторой, случайной позиции последовательности генерируемых чисел, и эта позиция определит номер сообщения, которое будет выведено на экран. Напомню, что функция kbhit () представляет собой довольно распространенное расширение библиотеки функций C++, обеспечиваемое многими компиляторами, но не входит в стандартный пакет библиотечных функций C++.
В следующем примере используется двумерный массив указателей для создания программы, которая отображает синтаксис-памятку по ключевым словам C++. В программе инициализируется список указателей на строки. Первая размерность массива предназначена для указания на ключевые слова C++, а вторая — на краткое их описание.

Список завершается двумя нулевыми строками, которые используются в качестве признака конца списка. Пользователь вводит ключевое слово, а программа должна вывести на экран его описание. Как видите, этот список содержит всего несколько ключевых слов. Поэтому его продолжение остается за вами.

// Простая памятка по ключевым словам C++.

using namespace std;

«for», «for (инициализация; условие; инкремент)»,

«if», «if (условие) . else . «,

«while», «while (условие) . »,

// Сюда нужно добавить остальные ключевые слова C++.

«», «» // Список должен завершаться нулевыми строками.

for (i=0; *keyword[i][0]; i++)

if (!strcmp (keyword[i][0], str))

>
Вот пример выполнения этой программы.

Введите ключевое слово: for

for (инициализация; условие; инкремент)
В этой программе обратите внимание на выражение, управляющее циклом for. Оно приводит к завершению цикла, когда элемент keyword[i][0] содержит указатель на нуль, который интерпретируется как значение ЛОЖЬ. Следовательно, цикл останавливается, когда встречается нулевая строка, которая завершает массив указателей.
Соглашение о нулевых указателях
Объявленный, но не инициализированный указатель будет содержать произвольное значение. При попытке использовать указатель до присвоения ему конкретного значения можно разрушить не только собственную программу, но даже и операционную систему (отвратительнейший, надо сказать, тип ошибки!).

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

float *р = 0; // р — теперь нулевой указатель.
Для тестирования указателя используется инструкция if (любой из следующих ее вариантов).

if (р) // Выполняем что-то, если р — не нулевой указатель.

if (!p) // Выполняем что-то, если р — нулевой указатель.
Соблюдая упомянутое выше соглашение о нулевых указателях, вы можете избежать многих серьезных проблем, возникающих при использование указателей.
Указатели и 16-разрядные среды
Несмотря на то что в настоящее время большинство вычислительных сред 32-разрядные, все же немало пользователей до сих пор работают в 16-разрядных (в основном, это DOS и Windows 3.1) и, естественно, с 16-разрядным кодом. Эти операционные системы были разработаны для процессоров семейства Intel 8086, которые включают такие модификации, как 80286, 80386, 80486 и Pentium (при работе в режиме эмуляции процессора 8086). И хотя при написании нового кода программисты, как правило, ориентируются на использование 32-разрядной среды выполнения, все же некоторые программы по-прежнему создаются и поддерживаются в более компактных 16-разрядных средах. Поскольку некоторые темы актуальны только для 16-разрядных сред, программистам, которые работают в них, будет полезно получить информацию о том, как адаптировать «старый» код к новой среде, т.е. переориентировать 16-разрядный код на 32-разрядный.
При написании 16-разрядного кода для процессоров семейства Intel 8086 программист вправе рассчитывать на шесть способов компиляции программ, которые различаются организацией компьютерной памяти. Программы можно компилировать для миниатюрной, малой, средней, компактной, большой и огромной моделей памяти. Каждая из этих моделей по-своему оптимизирует пространство, резервируемое для данных, кода и стека. Различие в организации компьютерной памяти объясняется использованием процессорами семейства Intel 8086 сегментированной архитектуры при выполнении 16-разрядного кода. В 16-разрядном сегментированном режиме процессоры семейства Intel 8086 делят память на 16К сегментов.
В некоторых случаях модель памяти может повлиять на поведение указателей и ваши возможности по их использованию. Основная проблема возникает при инкрементировании указателя за пределы сегмента. Рассмотрение особенностей каждой из 16-разрядных моделей памяти выходит за рамки этой книги. Главное, чтобы вы знали, что, если вам придется работать в 16-разрядной среде и ориентироваться на процессоры семейства Intel 8086, вы должны изучить документацию, прилагаемую к используемому вами компилятору, и подробно разобраться в моделях памяти и их влиянии на указатели.
И последнее. При написании программ для современной 32-разрядной среды необходимо знать, что в ней используется единственная модель организации памяти, которая называется одноуровневой, несегментированной или линейной (flat model).
Многоуровневая непрямая адресация
Можно создать указатель, который будет ссылаться на другой указатель, а тот — на конечное значение. Эту ситуацию называют многоуровневой непрямой адресацией (multiple indirection) или использованием указателя на указатель. Идея многоуровневой непрямой адресации схематично проиллюстрирована на рис. 6.2.

Как видите, значение обычного указателя (при одноуровневой непрямой адресации) представляет собой адрес переменной, которая содержит некоторое значение. В случае применения указателя на указатель первый содержит адрес второго, а тот ссылается на переменную, содержащую определенное значение.
При использовании непрямой адресации можно организовать любое желаемое количество уровней, но, как правило, ограничиваются лишь двумя, поскольку увеличение числа уровней часто ведет к возникновению концептуальных ошибок.
Переменную, которая является указателем на указатель, нужно объявить соответствующим образом. Для этого достаточно перед ее именем поставить дополнительный символ «звездочка»(*). Например, следующее объявление сообщает компилятору о том, что balance — это указатель на указатель на значение типа int.

int **balance;
Необходимо помнить, что переменная balance здесь — не указатель на целочисленное значение, а указатель на указатель на int-значение.
Чтобы получить доступ к значению, адресуемому указателем на указатель, необходимо дважды применить оператор «*» как показано в следующем коротком примере.

// Использование многоуровневой непрямой адресации.

using namespace std;

>
Здесь переменная p объявлена как указатель на int-значеине, а переменная q — как указатель на указатель на int-значеине. При выполнении этой программы мы получим значение переменной х, т.е. число 10.
Проблемы, связанные с использованием указателей
Для программиста нет ничего более страшного, чем «взбесившиеся» указатели! Указатели можно сравнить с энергией атома: они одновременно и чрезвычайно полезны и чрезвычайно опасны. Если проблема связана с получением указателем неверного значения, то такую ошибку отыскать труднее всего.
Трудности выявления ошибок, связанных с указателями, объясняются тем, что сам по себе указатель не обнаруживает проблему. Проблема может проявиться только косвенно, возможно, даже в результате выполнения нескольких инструкций после «крамольной» операции с указателем. Например, если один указатель случайно получит адрес «не тех» данных, то при выполнении операции с этим «сомнительным» указателем адресуемые данные могут подвергнуться нежелательному изменению, и, что самое здесь неприятное, это «тайное» изменение, как правило, становится явным гораздо позже. Такое «запаздывание» существенно усложняет поиск ошибки, поскольку посылает вас по «ложному следу». К тому моменту, когда проблема станет очевидной, вполне возможно, что указатель-виновник внешне будет выглядеть «безобидной овечкой», и вам придется затратить еще немало времени, чтобы найти истинную причину проблемы.
Поскольку для многих работать с указателями — значит потенциально обречь себя на поиски ответа на вопрос «Кто виноват?», мы попытаемся рассмотреть возможные «овраги» на пути отважного программиста и показать обходные пути, позволяющие избежать изматывающих «мук творчества».
Неинициализированные указатели
Классический пример ошибки, допускаемой при работе с указателями, — использование неинициализированного указателя. Рассмотрим следующий фрагмент кода.

// Эта программа некорректна.

*р = х; // На что указывает переменная р?

>
Здесь указатель р содержит неизвестный адрес, поскольку он нигде не определен. У вас нет возможности узнать, где записано значение переменной х. При небольших размерах программы (например, как в данном случае) ее странности (которые заключаются в том, что указатель р содержит адрес, не принадлежащий ни коду программы, ни области данных) могут никак не проявляться, т.е. программа внешне будет работать нормально. Но по мере ее развития и, соответственно, увеличения ее объема, вероятность того, что р станет указывать либо на код программы, либо на область данных, возрастет. В один прекрасный день программа вообще перестанет работать. Способ не допустить создания таких программ очевиден: прежде чем использовать указатель, позаботьтесь о том, чтобы он ссылался на что-нибудь действительное!
Некорректное сравнение указателей
Сравнение указателей, которые не ссылаются на элементы одного и того же массива, в общем случае некорректно и часто приводит к возникновению ошибок. Никогда не стоит полагаться на то, что различные объекты будут размещены в памяти каким-то определенным образом (где-то рядом) или на то, что все компиляторы и операционные среды будут обрабатывать ваши данные одинаково. Поэтому любое сравнение указателей, которые ссылаются на различные объекты, может привести к неожиданным последствиям. Рассмотрим пример.

if (p1 < р2) . . .
Здесь используется некорректное сравнение указателей, поскольку C++ не дает никаких гарантий относительно размещения переменных в памяти. Ваш код должен быть написан таким образом, чтобы он работал одинаково устойчиво вне зависимости от того, где расположены данные в памяти.
Было бы ошибкой предполагать, что два объявленных массива будут расположены в памяти «плечом к плечу», и поэтому можно обращаться к ним путем индексирования их с помощью одного и того же указателя. Предположение о том, что инкрементируемый указатель после выхода за границы первого массива станет ссылаться на второй, совершенно ни на чем не основано и потому неверно. Рассмотрим этот пример внимательно.

>
Цель этой программы — инициализировать элементы массивов first и second числами от 0 до 19. Однако этот код не позволяет надеяться на достижение желаемого результата, несмотря на то, что в некоторых условиях и при использовании определенных компиляторов эта программа будет работать так, как задумано автором. Не стоит полагаться на то, что массивы first и second будут расположены в памяти компьютера последовательно, причем первым обязательно будет массив first. Язык C++ не гарантирует определенного расположения объектов в памяти, и потому эту программу нельзя считать корректной.
Не забывайте об установке указателей
Следующая (некорректная) программа должна принять строку, введенную с клавиатуры, а затем отобразить ASCII-код для каждого символа этой строки. (Обратите внимание на то, что для вывода ASCII-кодов на экран используется операция приведения типов.) Однако эта программа содержит серьезную ошибку.

// Эта программа некорректна.

using namespace std;

gets (p1); // Считываем строку.

// Выводим ASCII-значения каждого символа.

>while (strcmp (s, «конец»));

>
Сможете ли вы сами найти здесь ошибку?
В приведенном выше варианте программы указателю p1 присваивается адрес массива s только один раз. Это присваивание выполняется вне цикла. При входе в do-while-цикл (т.е. при первой его итерации) p1 действительно указывает на первый символ массива s. Но при втором проходе того же цикла p1 указатель будет содержать значение, которое останется после выполнения предыдущей итерации цикла, поскольку указатель p1 не устанавливается заново на начало массива s. Рано или поздно граница массива s будет нарушена.
Вот как выглядит корректный вариант той же программы.

// Эта программа корректна.

using namespace std;

p1 = s; // Устанавливаем p1 при каждой итерации цикла.

gets (p1); // Считываем строку.

// Выводим ASCII-значения каждого символа.

>while (strcmp (s, «конец»));

>
Итак, в этом варианте программы в начале каждой итерации цикла указатель p1 устанавливается на начало строки.
Узелок на память. Чтобы использование указателей было безопасным, нужно в любой момент знать, на что они ссылаются.

Источник: pydev.ru

Рейтинг
( Пока оценок нет )
Загрузка ...
Заработок в интернете или как начать работать дома