Сколько памяти занимает указатель c
Очень часто возникают задачи обработки массивов данных, размерность которых заранее неизвестна. В этом случае возможно использование одного из двух подходов:
- выделение памяти под статический массив, содержащий максимально возможное число элементов, однако в этом случае память расходуется не рационально;
- динамическое выделение памяти для хранение массива данных.
Для использования функций динамического выделения памяти необходимо описать указатель, представляющий собой начальный адрес хранения элементов массива.
Начальный адрес статического массива определяется компилятором в момент его объявления и не может быть изменен.
Для динамического массива начальный адрес присваивается объявленному указателю на массив в процессе выполнения программы.
Динамическое выделение памяти для одномерных массивов
Форма обращения к элементам массива с помощью указателей имеет следующий вид:
int a[10], *p; // описываем статический массив и указатель
int b;
p = a; // присваиваем указателю начальный адрес массива
. // ввод элементов массива
b = *p; // b = a[0];
b = *(p+i) // b = a[i];
Пример на Си : Организация динамического одномерного массива и ввод его элементов.
Результат выполнения программы:
Перераспределение памяти
Если размер выделяемой памяти нельзя задать заранее, например при вводе последовательности значений до определенной команды, то для увеличения размера массива при вводе следующего значения необходимо выполнить следующие действия:
- Выделить блок памяти размерности n+1 (на 1 больше текущего размера массива)
- Скопировать все значения, хранящиеся в массиве во вновь выделенную область памяти
- Освободить память, выделенную ранее для хранения массива
- Переместить указатель начала массива на начало вновь выделенной области памяти
- Дополнить массив последним введенным значением
Все перечисленные выше действия (кроме последнего) выполняет функция
- ptr - указатель на блок ранее выделенной памяти функциями malloc() , calloc() или realloc() для перемещения в новое место. Если этот параметр равен NULL , то выделяется новый блок, и функция возвращает на него указатель.
- size - новый размер, в байтах, выделяемого блока памяти. Если size = 0 , ранее выделенная память освобождается и функция возвращает нулевой указатель, ptr устанавливается в NULL .
Размер блока памяти, на который ссылается параметр ptr изменяется на size байтов. Блок памяти может уменьшаться или увеличиваться в размере. Содержимое блока памяти сохраняется даже если новый блок имеет меньший размер, чем старый. Но отбрасываются те данные, которые выходят за рамки нового блока. Если новый блок памяти больше старого, то содержимое вновь выделенной памяти будет неопределенным.
Пример на Си Выделить память для ввода массива целых чисел. После ввода каждого значения задавать вопрос о вводе следующего значения.
sizeof(void*);// Размер указателя на любой тип равен 8, почему так много? И можно ли как-нибудь узнать сколько под указатель выделено памяти не смотря в исходники?
Размер строки указателя
char *str = new char ; str = "123"; sizeof(str) - отображает размер указателя, а как вывести.
Размер указателя short int
Доброе утро. Возник вопрос почему short int = 8 байт? Он же должен занимать 2 байта в 32-х.
Размер указателя на разные типы данных
еще один вопрос к етой теме почуму придавая указателю * prt тип short int или double функция sizeof.
Когда-то в эпоху создания первых версий DOS, людям казалось, что 512 KB памяти, это необъятный объём, которого хватит навсегда. А сколько байт можно адресовать 64-х битным неотрицательным числом?
Некоторым, конечно. И в контексте персональных компьютеров. Но это были вполне серьёзные, люди которые знали сколько будет два в восьмой степени. Причем, не приблизительно, а абсолютно точно. А два в девятой казалось просто огромным.:yes:
Потому что указатель хранит адрес объекта в памяти. Четырех байт хватает на адресацию только четырех первых гигабайт. По современным стандартам маловато будет - оперативки может и все шестнадцать гиг стоять.
Потому что авторы компилятора/стандарта - ленивые скотины. *p - указатель на метод. Дополнительные восемь байт нужны под преобразования this, осуществляемые при использовании множественного наследования. Вот только множественным наследованием здесь и не пахнет.
размер указателя зависит от разрядности ОС, просто адрес какой нибудь последней ячейки памяти не поместиться в указатель меньшего размера(цыфры не влезут в диапазон значений), по этому то и есть такая проблемма, что когда ставишь 32х разрядную операционку на машину с 10 Гб RAM, работает только 2 Гб(кажется, точно не помню), а остальные просто стоят мертвым грузом, потому что к ним Ос обратиться не может из ограничений в индексации указателя.
когда ставишь 32х разрядную операционку на машину с 10 Гб RAM, работает только 2 Гб(кажется, точно не помню), а остальные просто стоят мертвым грузом, потому что к ним Ос обратиться не может из ограничений в индексации указателя.
Не совсем так. Используя сегментную модель памяти и PAE-костыль, ОС может адресовать аж 64 гигабайта (36-битовый адрес). Пользователь живет в плоской модели памяти, где может адресовать четыре гигабайта. Но Винда отжирает половину под личные нужды, так что пользователю остается только два гига. Впрочем, какими-то прыжками с бубном, помнится, системный резерв ужимался до гигабайта.
Тут не соглашусь, ибо я в свое время на ноутбук с 4мя гигами памяти по своей глупости ставил 32х битную винду, и в параметрах системы мне писалось "2Гб установлено, 1 с чем то доступно", на тот же ноутбук поставил туже винду, но 64х битный вариант, мне стало писать "4Гб установлено 3,89 доступно. Вывод напрашивается сам.
Потому что авторы компилятора/стандарта - ленивые скотины. *p - указатель на метод. Дополнительные восемь байт нужны под преобразования this, осуществляемые при использовании множественного наследования. Вот только множественным наследованием здесь и не пахнет.
Указатель типа "указатель на член класса" является контравариантным: его можно неявно преобразовывать вниз по иерархии, а также явно преобразовывать вверх по иерархии при помощи static_cast . Поэтому пытаться "подбирать" размер указателя путем анализа "видимой" части иерархии - бесполезная затея. Компилятор не знает, как выглядит иерархия за пределами того, что ему видно в данный момент и есть ли там множественное/виртуальное наследование и т.п. Компилятор не знает, не появится ли множественное/виртуальное наследование у наследников класса S . Поэтому такие указатели приходится всегда реализовывать самым общим образом.
Привет, Хабр! Представляю вашему вниманию перевод статьи "Pointers Are Complicated, or: What's in a Byte?" авторства Ralf Jung.
Этим летом я снова работаю над Rust фуллтайм, и я снова буду работать (помимо прочих вещей) над "моделью памяти" для Rust/MIR. Однако, прежде чем я заговорю о своих идеях, я наконец должен развеять миф, что "указатели просты: они являются просто числами". Обе части этого утверждения ошибочны, по крайней мере в языках с небезопасными фичами, таких как Rust или C: указатели нельзя назвать ни простыми, ни (обычными) числами.
Я надеюсь, что прочитав этот пост, вы согласитесь со мной относительно обоих утверждений.
В чем проблема с "указатели — это обычные числа"? Давайте рассмотрим следующий пример: (я использую C++ здесь, так как писать небезопасный код в C++ проще, чем в Rust, и небезопасный код — это как раз то место, где и появляются проблемы. Небезопасный Rust и C имеют все те же проблемы, что и C++).
Оптимизация последнего чтения y[0] с возвращением всегда 42 очень выгодна. Обоснование такой оптимизации — изменение x_ptr, которое указывает на x, не может изменить y.
Однако, имея дело с языками низкого уровня, такими как C++, мы можем нарушить это предположение, присвоив i значение y-x. Так как &x[i] — это то же самое, что и x+i, мы записываем 23 в &y[0].
Конечно, это не мешает C++ компиляторам делать такие оптимизации. Чтобы разрешить это, стандарт говорит, что наш код имеет UB.
Во-первых, не разрешается выполнять арифметические операции над указателями (как в случае с &x[i]), если в этом случае указатель выходит за любую из границ массива. Наша программа нарушает это правило: x[i] выходит за границы x, поэтому это является UB. Иными словами, даже вычисление значения x_ptr является UB, так что мы даже не доходим до того места, где мы хотим использовать этот указатель.
(Оказывается, i = y-x также является UB, так как разрешается вычитать только указатели, указывающие в место одного выделения памяти. Однако мы могли бы написать i = ((size_t)y — (size_t)x)/sizeof(int), чтобы обойти это ограничение.)
Но мы еще не закончили: это правило имеет единственное исключение, которое мы можем использовать в нашу пользу. Если арифметическая операция вычисляет значение указателя на адрес точно после конца массива, то все в порядке. (Это исключение необходимо для вычисления vec.end() для самых обычных циклов в C++98.)
Давайте немного изменим пример:
А теперь представьте, что x и y были выделены друг за другом, причем y имеет больший адрес. Тогда x_ptr указывает на начало y! Тогда условие истинно и присваивание происходит. При этом тут нет UB из-за выхода указателя за границы.
Кажется, что это не позволит провести оптимизацию. Однако стандарт C++ имеет другой туз в рукаве, чтобы помочь создателям компиляторов: на самом деле он не позволяет нам использовать x_ptr. Согласно тому, что говорится в стандарте про прибавление чисел к указателям, x_ptr указывает на адрес после последнего элемента массива. Он не указывает на конкретный элемент другого объекта, даже если они имеют одинаковый адрес. (По крайней мере это распространенная интерпретация стандарта, на основе которой LLVM оптимизирует этот код.)
И даже несмотря на то, что x_ptr и &y[0] указывают на один адрес, это не делает их одинаковым указателем, то есть они не могут быть использованы взаимозаменяемо: &y[0] указывает на первый элемент y; x_ptr указывает на адрес после x. Если мы заменим *x_ptr = 23 строкой *&y[0] = 0, мы изменим значение программы, даже несмотря на то, что два указателя проверялись на равенство.
Это стоит повторить:
То, что два указателя указывают на один адрес, не значит то, что они равны и могут быть использованы взаимозаменяемо.
Да, эта разница трудноуловима. На самом деле, это до сих пор вызывает различия в программах, скомпилированных с LLVM и GCC.
Также заметьте, что это правило "один после" — не единственное место в C/C++, где мы можем наблюдать такой эффект. Другой пример — ключевое слово restrict в C, которое может быть использовано для выражения того, что указатели не перекрываются (не равны):
Вызов test() вызывает UB, так как два доступа к памяти в foo не должны происходить по одному адресу. Заменив *y на *x в foo, мы изменим значение программы, и она больше не будет вызывать UB. Еще раз: несмотря на то, что x и y имеют один адрес, их нельзя использовать взаимозаменяемо.
Указатели — это определенно не просто числа.
Так что такое указатель? Я не знаю полный ответ. На самом деле, это открытая область для исследований.
Один важный момент: здесь мы рассматриваем абстрактную модель указателей. Безусловно, на настоящем комьютере указатели являются числами. Но настоящий компьютер не проводит те оптимизации, которые делают современные компиляторы C++. Если бы мы написали вышеприведенные программы на ассемблере, то там не было бы ни UB, ни оптимизаций. C++ и Rust применяют более "высокоуровневый" подход к памяти и указателям, ограничивая программиста в угоду компилятору. Когда требуется формально описать, что программист может и не может делать в этих языках, модель указателей как чисел разбивается вдребезги, так что нам нужно найти что-то еще. Это другой пример использования "виртуальной машины", отличающейся от реального компьютера в целях спецификации — идее, о которой я писал раньше.
Вот простое предложение (на самом деле эта модель указателей используется в CompCert и моей работе RustBelt, а также способ, согласно которому интерпретатор miri реализует указатели): указатель — это пара какого-то ID, однозначно определяющего область памяти (allocation), и смещение относительно этой области. Если написать это на Rust:
Операции добавления (вычитания) числа к указателю (из указателя) влияют только на смещение, и поэтому указатель никогда не может покинуть область памяти. Вычитание указателей возможно только в том случае, если они относятся к одной области памяти (в соответствии с C++).
(Как мы могли видеть, стандарт C++ применяет эти правила к массивам, а не областям памяти. Однако, LLVM применяет их на уровне областей.)
Оказывается (и miri показывает то же самое), что эта модель может хорошо послужить нам. Мы всегда помним, к какой области памяти относится указатель, поэтому мы можем отличить указатель "один после" одной области памяти от указателя на начало другой области. Таким образом miri может обнаружить, что наш второй пример (с &x[8]) имеет UB.
В нашей модели указатели хоть и не являются числами, но они хотя бы простые. Однако эта модель начнет разваливаться на глазах, как только вы вспомните приведения указателей к числам. В miri приведение указателя к числу на самом деле ничего не делает, мы просто получаем числовую переменную (т. е. ее тип говорит, что это число), чье значение является указателем (т. е. пара область памяти и смещение). Однако, умножение этого числа на 2 ведет к ошибке, так как совершенно непонятно, что значит "умножить такой абстрактный указатель на 2".
Я должен пояснить: это не хорошее решение, если речь заходит об определении семантики языка. Однако, это хорошо работает для интерпретатора. Это самый простой подход, и мы выбрали его, потому что непонятно, как это можно сделать иначе (кроме как не поддерживать такие приведения вовсе — но с их поддержкой miri может запускать больше программ): в нашей абстрактной машине нет единого "адресного пространства", в котором располагались бы все выделенные области памяти, а все указатели были сопоставлены с конкретными различными числами. Каждая область памяти идентифицируется (скрытым) ID. Теперь мы можем начинать добавлять в нашу модель дополнительные данные вроде базового адреса для каждой области памяти, и каким-то образом использовать это для приведения числа обратно к указателю… и на этом моменте процесс становится действительно очень сложным, и, в любом случае, обсуждение этой модели не является целью написания поста. Его цель — обсудить необходимость такой модели. Если вы заинтересованы, я рекомендую вам прочитать этот документ, который подробнее рассматривает вышеприведенную идею добавления базового адреса.
Короче говоря, приведения указателей и чисел друг к другу запутанные, и их сложно определить формально, учитывая обсужденные выше оптимизации. Возникает конфликт между высокоуровневым подходом, необходимым для оптимизаций, и низкоуровневым подходом, необходимым для описания приведения указателей к числам и обратно. По большей части мы просто игнорируем эту проблему в miri и по возможности стараемся делать как можно больше, используя простую модель, с которой мы работаем. Полное определение языков таких, как C++ или Rust, разумеется, не может пойти по такому простому пути, оно должно объяснять, что происходит в действительности. Насколько мне известно, не существует подходящего решения, но академические изыскания приближаются к истине.
Именно поэтому указатели также не являются и простыми.
Я надеюсь, я привел достаточно убедительный довод, что числа — не единственный тип данных, которые нужно учитывать, если мы хотим формально описать низкоуровневые языки вроде C++ или (небезопасную часть) Rust. Однако это значит, что простая операция вроде чтения байта из памяти не может просто вернуть u8. Представьте себе, что мы реализуем memcpy, читая по очереди каждый байт источника в какую-то локальную переменную v, а затем сохраняем это значение в целевом месте. А что, если этот байт является частью указателя? Если указатель — это пара ID области памяти и смещения, то каков будет его первый байт? Нам нужно сказать, чему равно значение v, поэтому нам придется как-то ответить на этот вопрос. (И это совершенно иная проблема, чем проблема с умножением, которая была в предыдущей секции. Мы просто предположим, что существует некий абстрактный тип Ponter.)
Например, PtrFragment(ptr, 0) представляет собой первый байт указателя ptr. Таким образом, memcpy может "разбить" указатель на отдельные байты, представляющие этот указатель в памяти, и копировать их по отдельности. На 32-битной архитектуре полное представление ptr будет содержать 4 байта:
Такое представление поддерживает все операции по перемещению данных над указателями на уровне байтов, чего вполне достаточно для memcry. Арифметические или битовые операции полностью не поддерживаются; как было отмечено выше, это потребовало бы более сложное представление указателей.
Однако мы не закончили с нашим определением "байта". Чтобы полностью описать поведение программы, нам нужно учесть еще один вариант: байт в памяти может быть неинициализирован. Последнее определение байта будет выглядеть так (предположим, у нас есть тип Pointer для указателей):
Мы используем значение Uninit для всех байтов в выделенной памяти, в которые мы еще не записали какое-либо значение. Можно без проблем читать неинициализированную память, но любые другие действия с этими байтами (например, числовая арифметика) приводит к UB.
Это очень похоже на правила LLVM по отношению к специальному значению poison. Заметьте, что LLVM также имеет значение undef, которое используется для неинициализированной памяти и работает несколько иначе. Однако, компиляция нашего Uninit в undef корректна (undef в каком-то смысле "слабее"), и есть предложения убрать undef из LLVM и использовать вместо него poison.
Вы можете удивиться, почему у нас вообще есть специальное значение Uninit. Почему бы не выбрать какое-нибудь произвольное b: u8 для каждого нового байта, и затем использовать Bits(b) в качестве изначального значения? Это действительно является одним из вариантов. Однако, прежде всего, все компиляторы пришли к подходу с использованием специального значения для неинициализированной памяти. Не следовать этому подходу значит не только вызвать проблемы с компиляцией через LLVM, но и пересмотреть все оптимизации и убедиться, что они работают корректно с этой измененной моделью. Ключевой момент здесь: всегда можно безопасно заменить Uninit любым другим значением: любая операция, получающая это значение, в любом случае приводит к UB.
Например, этот код на C проще оптимизировать с Uninit:
С Uninit мы можем с легкостью сказать, что x имеет либо значение Uninit, либо значение 1, и раз замена Uninit на 1 работает, оптимизация легко объясняется. Без Uninit x равно либо "какому-то произвольному битовому паттерну", либо 1, и проведение той же оптимизации уже сложнее объяснить.
(Мы можем возразить, что мы можем поменять местами операции, когда делаем недетерминированный выбор, но тогда нам надо будет доказать, что трудный для анализа код не использует никаким образом x. Uninit позволяет избегать этой мороки с ненужными доказательствами.)
Наконец, Uninit является лучшим выбором для интерпретаторов вроде miri. Такие интерпретаторы имеют проблемы с операциями типа "просто выбери любое из этих значений" (т. е. недетерминированными операциями), так как они стремятся пройти все возможные пути выполнения программы, а это значит, что им необходимо попробовать все возможные значения. Использование Uninit вместо произвольного битового паттерна значит, что miri может после одного выполнения программы сказать вам, использует ли ваша программа неинициализированные значения некорректно.
Мы увидели, что в языках вроде C++ и Rust (в отличие от реальных компьютеров) указатели могут быть различны, даже если они указывают на один адрес, и что байт — это нечто большее, чем просто число из диапазона 0..256. Поэтому если в 1978 году язык C можно было "портативным ассемблером", то сейчас это невероятно ошибочное утверждение.
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Указатели широко используются в программировании на языке Си.
Указатели часто используются при работе с массивами.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет свой адрес — номер первой ячейки, где она расположена, а также свое значение. Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен.
Общая форма объявления указателя
Тип указателя — это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
- операция * (звездочка) — позволяет получить значение объекта по его адресу — определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
- операция & (амперсанд) — позволяет определить адрес переменной.
Для указанного примера обращение к одним и тем же значениям переменной и адреса представлено в таблице
Результат выполнения программы:
Расположение в памяти переменной a и указателя b:
Необходимо помнить, что компиляторы высокого уровня поддерживают прямой способ адресации: младший байт хранится в ячейке, имеющей младший адрес.
В связке Geany и GCC на Windows %x необходимо заменить %p. И будет такой вопрос, как эту связку научить выводить русские шрифты, может кто использует.
Здраствуйте, есть задание записать по указанному адресу указанное значение (без использования переменных). Это возможно? Как?
Здравствуйте, Елена. Можете объяснить как работает 5-я строчка, что-то напоминает преобразование типов, но я не понимаю:
uint32_t convert(uint8_t a, uint8_t b, uint8_t c, uint8_t d)
uint32_t result=0; //сюда мы будем собирать результат
uint8_t *i; //указатель
i = (uint8_t *) (&result); //считываем адрес куда помещаем //Как оно работает.
*i = pgm_read_byte(digits+a); //преобразовали в соотв. код сегментов и кинули первую цифру
i++; //шагнули на один байт(потому что тип uint8_t) в памяти
*i = pgm_read_byte(digits+b); //преобразовали в соотв. код сегментов и кинули вторую цифру
i++; //шагнули на один байт(потому что тип uint8_t) в памяти
*i = pgm_read_byte(digits+c); //преобразовали в соотв. код сегментов и кинули третью цифру
i++; //шагнули на один байт(потому что тип uint8_t) в памяти
*i = pgm_read_byte(digits+d); //преобразовали в соотв. код сегментов и кинули четвертую цифру
return result; //вернули результат
>
i указывает на ту ячейку, в которой находится result, только при разыменовании будет получать 1-байтные данные
Спасибо за ответ! Извините, что с комментами "пристреливался". Но все равно мало понятен смысл сего выражения, механизм то я примерно понимаю. Может его как-то можно записать в формате для дурачков? Как пример data >>= 1, на самом деле будет data = (data >> 1).
Здравствуйте.У меня дилетантский вопрос по указателям.Есть несколько статических массивов типа: unsigned char[x].Мне нужно объединить эти массивы в один большой массив.Причем эти массивы будут повторятся несколько раз в большом массиве.Можно это сделать с помощью указателей? или это возможно только перезаписью элементов массива через циклы.В других языках есть специальные функции добавить массив в массив,сложить элементы массивов и др. Есть ли такие функции в библиотеках С или самому писать?
Елена, Добрый день! У меня есть программа на Си для работы с оборудованием, точнее с электронным табло на которое в режиме on-line выводятся данные. Программа рабочая, но периодически появляются ошибки. Подозреваю, что не очень корректно происходит работа с указателями. Можно получить Вашу консультацию по этому вопросу? Заранее спасибо за ответ.
Все материалы размещены на сайте. Индивидуальные консультации готова обсуждать в индивидуальном порядке, а не через комментарии. Пишите в форму обратной связи или в ВК
Добрый вечер. Задача такая. Есть две функции, одна кодирует строку из восьми символов, другая раскодирует то что было закодировано (используется алгоритм шифрования/дешифрования TEA). Шифрация и дешифрация проходят успешно. Проблема такая: функция дешифрации uint32_t* decrypt(uint32_t* v, uint32_t* k)возвращает указатель на массив uint32_t* decoded = decrypt(plain, key); В возвращаемом массиве 2 тридцатидвухбитных числа, которые и есть результат раскодирования. Как имея эти два числа получить символьную строку чтобы было видно, что строки на входе и на выходе совпадают. Строку надо поместить в массив из восьми элементов типа char.
// Функция кодирования текста
uint32_t* encrypt(uint32_t* v, uint32_t* k)
<
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t sum = 0;
/* a key schedule constant */
uint32_t delta = 0x9e3779b9;
/* cache key */
uint32_t k0 = k[0];
uint32_t k1 = k[1];
uint32_t k2 = k[2];
uint32_t k3 = k[3];
uint32_t i;
return v; // Возвращаем указатель на нулевой элемент массива зашифрованного числа
// Функция декодирования текста
uint32_t* decrypt(uint32_t* v, uint32_t* k)
/* set up */
uint32_t v0 = v[0];
uint32_t v1 = v[1];
uint32_t sum = 0xC6EF3720;
uint32_t i;
/* a key schedule constant */
uint32_t delta = 0x9e3779b9;
/* cache key */
uint32_t k0 = k[0];
uint32_t k1 = k[1];
uint32_t k2 = k[2];
uint32_t k3 = k[3];
plain = (uint32_t*)shelf1; // Загружаем текст в plain
uint32_t* encoded = encrypt(plain, key); // Шифруем текст
uint32_t* decoded = decrypt(plain, key); // Расшифровываем текст
Правильно я понимаю, что функция decrypt возвращает указатель на первый элемент массива v ? Как расшифрованный текст поместить в символьный массив shelf2 ?
Да, указатель на массив - это указатель на его начальный элемент. Текст можно переместить в массив посимвольным копированием. Либо передать указатель на результирующий массив в функцию и там его заполнить.
Массив v имеет тип uint32_t. В двух элементах этого массива содержаться два числа. Как эти два числа преобразовать в текстовый массив ?
В последней строке текста, как я понимаю, в указатель key загружается адрес первого элемента массива key_buffer. Елена, а зачем перед key_buffer прописано (uint32_t*) ? Что это означает ?
uint32_t* decrypt(uint32_t* v, uint32_t* k)
/* set up */
uint32_t v0 = v[0];
uint32_t v1 = v[1];
Функция возвращает указатель decoded на массив uint32_t v[2]. Как используя этот указатель перенести массив v[2] в символьный массив char mas2[8].
В продолжение к предыдущему. Елена,корректны ли эти две строки ? Здесь, как я понимаю, в первой строке открывается файл на запись. Во второй строке при помощи указателя decoded, указывающего на массив v[2] из функции в файл что пишется ? Прокоментируйте, пожалуйста подробно, обе строки.
plain - это указатель. Прежде чем использовать указатель надо определить его значение, т.е. занести туда адрес переменной. В программе значение p не задано. В связи с этим возникает вопрос: В операторе uint32_t* plain = p; p куда пишется ?
Есть такая часть кода SysMemBootJump = (void (*)(void)) (*((uint32_t *)(addr + 4))); Я понимаю что этот метод позволяет выполнить прыжок в другую область памяти, начиная с addr + 4. Всё прекрасно работает. Но мне не понятны эти действия с указателями, помогите, пожалуйста, разобраться с этим механизмом
Значение, которое хранится по адресу addr+4, используется как указатель на uint32_t (адрес), и идёт обращение к значению по этому адресу, которое, в свою очередь, является адресом функции, не принимающей аргументов и возвращающей указатель, который присваивается SysMemBootJump
Запутался совсем. Надо организовать многоуровневое меню. У каждого пункта в корневом (0) уровне имеется свой индивидуальный набор подпунктов. Надо как-то универсально по индексам работать с 1м и последующими уровнями и чтобы не расходовать память впустую. у меня есть такие описатели: char * menu1_1[4] = < "пункт1", "пункт2", "абвгд", "иклмн" >; char * menu1_2[2] = < "пункт10", "пункт14" >; char * menu1_3[6] = < "п/п2", "пунктХ", "абвгд", "п/п3", "п/п4", "п/п5">; char * menu1_4[26] = < "п/п4", . , "п/п30">; // я тут сократил многоточием char *** mnu1; // вот здесь вот проблема //например если в корневом меню выбран 3й пункт, тогда mnu1 = &menu1_3; // и здесь проблема mnu1_length = 6; чтобы потом получить индексированный доступ к нужным строкам наподобие i=2; t14.txt = **mnu1[i]; // должен дать указатель на строку "пунктХ". Всем моим функциям надо кормить только указатели на строки для работы. можно конечно использовать многомерные массивы (как у меня сейчас), но получается ощутимый расход памяти впустую (большая система меню). Нигде не нашел никакого подобного решения на си.
Стандартная задача проектирования многоуровневого меню для сайтов. Есть массив структур или база данных вида
id, строка текста, id родителя
Возможно, ещё тип или какие-то вспомогательные поля. У верхнего уровня id родителя 0, остальные id нумеруются с 1. Сначала строим главное меню с id 0. Если выбран какой-то пункт, строим вложенное меню из тех пунктов, которые имеют родителем выбранный пункт
Ну да. И каждый раз эту базу полностью перелопачивать чтобы обеспечить скроллинг, либо делать вспомогательные массивы (текущее меню и порядок входа). Плюс расход на каждую строку текста по 2 id, ну и еще 2 id, показывающие предыдущий и последующий пункт меню. В общем как я понял указателями в си такую задачу не решить (указатель на массив указателей на строки). Только структура+массивы+индексы.
Я тоже мучался с указателями 3 месяца. Понял только сегодня! Итак, что сбивало с толку, например здесь я _неправильно думал_ что
То есть я думал, что *p - "звездочкаП" это некое неделимое имя, что НЕВЕРНО! Все сразу понимается четко и верно, как только поставить пробел между звездочкой и p.
Здесь сразу понимаешь, что int * это определитель типа данных (который, кстати можно записать и как int*), а вот после того как тип данных определен, уже идет присваивание к имени p адреса переменной a, что естественно, так как имени p присвоен тип указатель (который может хранить только адрес), прохождение к этому адресу ведет к значению int, лежащему по этому адресу.
Я это долго не мог понять, пока не наткнулся на двойные типы даных, такие как long long и мне сразу стала понятна конструкция int *p=&a; просто еще раз говорю, для лучшего понимания ставьте мысленно пробел между *_и_p, это сразу будет наглядно и понятно! int *p=&a; превращается в int * p=&a;
Надеюсь это поможет многим в понимании как понять запись указателей!
Не надо никаких "мысленных" пробелов! Я всегда пишу так: int* p = &a; и сразу видно что тип p - это именно "указатель на int".
Я как понял int a[]; и int *arr; это одно и тоже? Почему тогда такая запись возможна: int *arr[]; а такая нет int **arr; это же должно соответствовать записи int arr[][]; Хотелось бы, чтобы вы прокомментировали все эти 5 типов записей, и как они взаимосвязаны
Следует различать статические массивы, которые описываются с помощью квадратных скобок, и динамические массивы, для описания которых используются указатели. Для статического массива память выделяется в момент объявления. Для динамического массива - с помощью специальных функций динамического выделения памяти (malloc(), new).
int arr[10]; // одномерный статический массив из 10 элементов типа int
int *arr; // указатель, которому может быть присвоен адрес начала динамического массива
int *arr[10]; // массив из 10 указателей, каждому из которых должен быть присвоен адрес своей выделенной области памяти
int **arr; // указатель на указатель, то есть массив указателей (см.строчку выше), размер которого тоже будет определен позднее
int arr[10][10]; // двумерный статический массив
Помогите разобраться, у меня не компилировался свой код, потом попробовал ваш код из темы копи-пастнуть, ошибка аналогичная. Подскажите пожалуйста, что ему не нравится? vladislav@My-Comp:~/Рабочий стол/CodeC$ gcc pointers.c -o pointers.exe pointers.c: In function ‘main’: pointers.c:12:62: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=] printf("\n Адрес переменной a равен %x шестн.", &a); ~^ ~~ %ls pointers.c:14:66: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int *’ [-Wformat=] printf("\n Значение указателя b равно %x шестн.", b); ~^ %ls pointers.c:15:85: warning: format ‘%x’ expects argument of type ‘unsigned int’, but argument 2 has type ‘int **’ [-Wformat=] ntf("\n Адрес расположения указателя b равен %x шестн.", &b); ~^ ~~
Судя по всему, Ваш компилятор в шестнадцатеричном виде желает выводить только числа типа unsigned int. Чтобы не было ошибки, нужно явно привести все указатели к этому типу.
getchar() нужен, чтобы задержать консоль (чтобы программа сразу не закрылась, и пользователь мог посмотреть результат)
void helloWorld (GtkWidget *wid, GtkWidget *win)
GtkWidget *dialog = NULL ;
dialog = gtk_message_dialog_new (GTK_WINDOW (win), GTK_DIALOG_MODAL,
GTK_MESSAGE_INFO, GTK_BUTTONS_CLOSE, sqlite3_libversion());
gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
gtk_dialog_run (GTK_DIALOG (dialog));
gtk_widget_destroy (dialog);
>
int main ( int argc, char *argv[])
GtkWidget *button = NULL ;
GtkWidget *win = NULL ;
GtkWidget *vbox = NULL ;
/* Initialize GTK+ */
g_log_set_handler ( "Gtk" , G_LOG_LEVEL_WARNING, (GLogFunc) gtk_false, NULL );
gtk_init (&argc, &argv);
g_log_set_handler ( "Gtk" , G_LOG_LEVEL_WARNING, g_log_default_handler, NULL );
/* Create the main window */
win = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_container_set_border_width (GTK_CONTAINER (win), 8);
gtk_window_set_default_size (GTK_WINDOW (win), 400, 200);
gtk_window_set_title (GTK_WINDOW (win), "Hello World" );
gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_CENTER);
gtk_widget_realize (win);
g_signal_connect (win, "destroy" , gtk_main_quit, NULL );
/* Create a vertical box with buttons */
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_container_add (GTK_CONTAINER (win), vbox);
button = gtk_button_new_from_stock (GTK_STOCK_DIALOG_INFO);
g_signal_connect (G_OBJECT (button), "clicked" , G_CALLBACK (helloWorld), (gpointer) win);
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);
button = gtk_button_new_from_stock (GTK_STOCK_CLOSE);
g_signal_connect (button, "clicked" , gtk_main_quit, NULL );
gtk_box_pack_start (GTK_BOX (vbox), button, TRUE, TRUE, 0);
/* Enter the main loop */
gtk_widget_show_all (win);
gtk_main ();
return 0;
>
Я немного смущен указателями и тем, сколько байтов они занимают. В моем учебнике сначала говорится, что указатели в 16-битных системах занимают 2 байта, 32-битные системы - 4 байта, 64-битные системы - 8 байтов и так далее. Затем через 10 строк говорится, что указатели занимают столько байтов, сколько необходимо для хранения адресов. Вот мои вопросы:
- Значит ли это, что, скажем, в 64-битной системе для адреса потребуется не более 8 байт?
- Если мы находимся в 16-битной системе и указатели занимают 2 байта, а для адреса требуется еще 2 байта, что произойдет?
Нет однозначного ответа; это полностью зависит от архитектуры, реализации компилятора и даже от типа самого указателя. Указатели на разные типы не гарантируют одинаковый размер и / или представление.
Например, предположим, что архитектура с адресацией слов, где наименьшая адресуемая единица хранения имеет ширину 16 бит (или больше). Каждое слово может содержать несколько char значений; все другие типы занимают слово или больше. В такой архитектуре a char * и void * потребуются дополнительные биты для смещения в слово по сравнению с другими типами указателей.
Также обратите внимание, что тип указателя может быть шире, чем количество битов, фактически необходимых для хранения адреса. Оригинальный Macintosh работал на процессоре Motorola 68000, который имел 32-битный размер слова, но только 24 бита на адресной шине. Типы указателей имели ширину 32 бита, верхние 8 бит оставались неиспользованными. Предприимчивые программисты MacOS воспользовались этим, чтобы сохранить некоторые данные в самом верхнем байте типа указателя, максимально используя эти драгоценные 128 КБ ОЗУ. Конечно, Motorola в конечном итоге выпустила ЦП с 32 адресными строками (68020), что означало, что весь этот код пришлось переписать.
На современном стандартном настольном и серверном оборудовании (читай: x86) достаточно безопасно предположить, что все типы указателей имеют тот же размер, что и собственный размер слова (32- или 64-разрядный), и что все типы указателей имеют одинаковый размер. и представительство. Просто знайте, что это не должно быть правдой.
Динамическое выделение памяти для двумерных массивов
Пусть требуется разместить в динамической памяти матрицу, содержащую n строк и m столбцов. Двумерная матрица будет располагаться в оперативной памяти в форме ленты, состоящей из элементов строк. При этом индекс любого элемента двумерной матрицы можно получить по формуле
index = i*m+j;
где i - номер текущей строки; j - номер текущего столбца.
Рассмотрим матрицу 3x4 (см. рис.)
Индекс выделенного элемента определится как
index = 1*4+2=6
Объем памяти, требуемый для размещения двумерного массива, определится как
n·m·(размер элемента)
Однако поскольку при таком объявлении компилятору явно не указывается количество элементов в строке и столбце двумерного массива, традиционное обращение к элементу путем указания индекса строки и индекса столбца является некорректным:
Правильное обращение к элементу с использованием указателя будет выглядеть как
- p - указатель на массив,
- m - количество столбцов,
- i - индекс строки,
- j - индекс столбца.
Пример на Си Ввод и вывод значений динамического двумерного массива
Результат выполнения
Возможен также другой способ динамического выделения памяти под двумерный массив - с использованием массива указателей. Для этого необходимо:
- выделить блок оперативной памяти под массив указателей;
- выделить блоки оперативной памяти под одномерные массивы, представляющие собой строки искомой матрицы;
- записать адреса строк в массив указателей.
Графически такой способ выделения памяти можно представить следующим образом.
При таком способе выделения памяти компилятору явно указано количество строк и количество столбцов в массиве.
Пример на Си
Результат выполнения программы аналогичен предыдущему случаю.
С помощью динамического выделения памяти под указатели строк можно размещать свободные массивы. Свободным называется двухмерный массив (матрица), размер строк которого может быть различным. Преимущество использования свободного массива заключается в том, что не требуется отводить память компьютера с запасом для размещения строки максимально возможной длины. Фактически свободный массив представляет собой одномерный массив указателей на одномерные массивы данных.
Для размещения в оперативной памяти матрицы со строками разной длины необходимо ввести дополнительный массив m , в котором будут храниться размеры строк.
Пример на Си : Свободный массив
Стандартные функции динамического выделения памяти
Функции динамического выделения памяти находят в оперативной памяти непрерывный участок требуемой длины и возвращают начальный адрес этого участка.
Функции динамического распределения памяти:
Для использования функций динамического распределения памяти необходимо подключение библиотеки :
Поскольку обе представленные функции в качестве возвращаемого значения имеют указатель на пустой тип void , требуется явное приведение типа возвращаемого значения.
Для определения размера массива в байтах, используемого в качестве аргумента функции malloc() требуется количество элементов умножить на размер одного элемента. Поскольку элементами массива могут быть как данные простых типов, так и составных типов (например, структуры), для точного определения размера элемента в общем случае рекомендуется использование функции
Память, динамически выделенная с использованием функций calloc(), malloc() , может быть освобождена с использованием функции
«Правилом хорошего тона» в программировании является освобождение динамически выделенной памяти в случае отсутствия ее дальнейшего использования. Однако если динамически выделенная память не освобождается явным образом, она будет освобождена по завершении выполнения программы.
Читайте также: