Этюды для начинающих: Память 2. Статические переменные.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

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

В прошлый раз мы говорили, что память под данные может выделяться статически и динамически. Сегодня поговорим о статически выделяемой памяти. Из самого названия «статическая» следует, что это память, выделяемая на постоянной основе. Она выделяется и инициализируется один раз, в самом начале работы программы, ещё до выполнения любых Ваших функций (способ что-то сделать до инициализации статической памяти существует, и мы его рассматривали, но это гнусный хак и залезание под юбку системе, поэтому здесь мы его с негодованием отвергнем).

Существует два способа описать статическую переменную: объявить её внутри любой функции (блока) с ключевым словом static или же объявить её вне всякой функции (в глобальном пространстве) со словом static или без него. В первом случае - это локальная внутри функции переменная, а во втором глобальная, но обе они статические.

Обратите внимание, что глобальная переменная всегда является статической, независимо от наличия или отсутствия слова static в её объявлении. Слово static  в описании глобальной переменной служит лишь для того, чтобы сделать её невидимой из других файлов (если проект состоит из нескольких файлов).

Исключительно для краткости, впредь я буду называть глобальные переменные просто глобальными, а статические, объявленные внутри функций, просто статическими. Но это делается исключительно для краткости текста, чтобы отличать эти переменные друг от друга меньшим количеством слов – по механизму выделения памяти они все статические и ничем друг от друга не отличаются.

Все глобальные и статические переменные характеризуются следующими общими свойствами:

1. Память под них выделяется один раз, и нет никакой возможности её освободить или изменить её размер. Её можно посторно использовать, но только на том же месте.

2. Инициализируется такая переменная только один раз перед началом выполнения программы.

Пример:

int counter = 0;

void Foo(void) {
    int current = 321;
    static int voltage = 0;
    //...
}

Переменная counter - глобальная. Переменная current - динамическая, она будет получать значение 321 всякий раз при входе в функцию Foo, в которой она описана. Переменная же voltage – статическая. Она получит значение 0 один раз перед началом исполнения программы. Таким образом, если переменная current внутри функции изменяется, при следующем входе в функцию все изменения будут забыты, и она снова станет равна 321. А вот если внутри функции изменяется переменная voltage, то её изменения будут сохраняться между вызовами функции. Обратите внимание на последнюю фразу. Это одна из основных причин использовать статические переменные.

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

Правила области видимости таковы:

1. Никакую переменную нельзя использовать до того, как она объявлена.

2. Область видимости глобальной переменной – от точки объявления и до конца файла, в котором она объявлена (особенности многофайловых проектов мы здесь не рассматриваем).

3. Область видимости статической переменной – от точки объявления и до конца блока, в котором она объявлена (под блоком понимается часть программы, заключённая в фигурные скобки или тело цикла, если переменная объявлена в заголовке цикла for).

4. Нельзя объявлять две переменные с одинаковыми именами на глобальном уровне или в одном и том же блоке;

5. Переменные с одинаковыми именами, объявленные в разных блоках – суть разные переменные;

6. Переменная, объявленная в блоке «экранирует» переменную с тем же именем, объявленную в объемлющем блоке или глобальном пространстве.

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

Сначала поясним экранирование на самом простом примере:

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

int counter = 321;

void setup(void) {
	Serial.begin(115200);
	{
		Serial << "1. counter: " << counter << '\n';
		static int counter = 123;
		Serial << "2. counter: " << counter << '\n';
	}
	Serial << "3. counter: " << counter << '\n';
}

void loop(void) {}

У нас имеется глобальная переменная counter (строка 3). В функции setup имеется блок (строки 7-11). В блоке объявлена другая переменная counter (строка 9). Теперь внимание. В строке 8 мы печатаем counter и, поскольку новая, экранирующая, переменная ещё не объявлена, печатается значение глобальной переменной. Далее, в строке 9 мы объявляем экранирующую переменную и в строке 10 печатаем уже её!. После этого блок закрывается и вместе с ним заканчивается область видимости переменной, объявленной в строке 9. Поэтому в строке 12 снова печатается значение глобальной переменной.

Результат выполнения этого скетча:

1. counter: 321
2. counter: 123
3. counter: 321

Другой пример, который заодно показывает, что статическая переменная сохраняет свой значение между вызовами функции.

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

int counter = 321;

void dummyFunction(const int n) {
	static int counter = 123;
	Serial << n << ". counter: " << counter << '\n';
	counter ++;
}

void setup(void) {
	Serial.begin(115200);
	Serial << "1. counter: " << counter << '\n';
	dummyFunction(2);
	Serial << "3. counter: " << counter << '\n';
	dummyFunction(4);
	Serial << "5. counter: " << counter << '\n';
}

void loop(void) {}

Результат этого примера:

1. counter: 321
2. counter: 123
3. counter: 321
4. counter: 124
5. counter: 321

Обратите внимание на то, что внутри функции, после печати мы увеличили статическую переменную на 1, поэтому при следующем вызове функции напечаталось значение на 1 больше, т.е. она сохраняет значение.

Как видите, суть экранирования в том, что если во внутреннем блоке объявить переменную, которая уже объявлена во внешнем, переменная из внутреннего блока заменит переменную из внешнего. А после завершения внутреннего блока, переменная, объявленная во внешнем вновь вступит в свои права.

Первая мысль, возникающая при знакомстве с экранированием –  а нафига это нужно? Разве что для того, чтобы смертельно запутать программу и сделать её абсолютно нечитабельной и непонятной?

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

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

Как всем этим пользоваться?

Имена переменных

Имя переменной должно сообщать информацию о том, что это за переменная. Помните, замечательные слова В.В. Маяковского:

«Если тебе корова имя,
У тебя должно быть молоко и вымя.
А если нет ни молока, ни вымени,
То чёрта-ль в твоём коровьем имени?».

Это же относится и к переменным. Если переменная называется voltage, то лучше бы ей содержать значение напряжения, а не температуры поверхности Марса. Хотя, конечно, сам по себе язык программирования никаких ограничений здесь не накладывает.

Это касается любых переменных, но глобальных – в особенности. Если Вы заведёте глобальную переменую с ничего не говорящим однобуквенным именем, Вы очень быстро забудете, что это за переменная и, кроме того, велика вероятность испортить её случайным присваиванием или заэкранировать там, где это не нужно.

Нет никаких соображений (любимая эффективность и т.п.), мешающих Вам использовать «говорящие» имена переменных.

Называйте переменные так, чтобы было понятно, что они означают.

Когда использовать глобальные и статические переменные?

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

Глобальные переменные – это очень опасная вещь из-за своей чрезвычайно большой области видимости. Любое случайное изменение глобальной переменной может аукнуться в любом месте программы. Искать такие ошибки весьма непросто – их проще не допускать. Поэтому общая рекомендация – следует ограничивать, а по возможности, исключать вовсе, использование глобальных переменных.

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

Статическая переменная внутри функции нужна в том случае, когда требуется, чтобы переменная сохраняла своё значение между вызовами функции.

Если переменная, которая должна сохранять своё значение, используется только в одной функции и ни в какой другой – она должна объявляться внутри функции со словом static.

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

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

Например, вот такая запись является признаком либо безграмотности, либо небрежности (возможно, и того, и другого).

unsigned long savedMillis = 0;

void loop(void) {
   if (millis() - savedMillis >= INTERVAL) {
      //  .....
   }
}
т.к. переменная savedMillis здесь оставлена глобальной совершенно необоснованно. Она используется только в одной функции, так какого чёрта ей быть глобальной? Чтобы случайно ихменить её где-нибудь в 500 строках от этого места, где тоже потребуется работать с задержками по millis?

Правильно было бы сделать её локальной - статической:

void loop(void) {
	static unsigned long savedMillis = 0;
	if (millis() - savedMillis >= INTERVAL) {
		// .....
	}
}

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

Приложение для параноиков

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

Но это именно для параноиков. На практике такой приём широко используется на большие машинах, где программы могут состоять из миллионов строки и тысяч файлов, а разрабатывают их десятки программистов. В Ардуино таких проектов не бывает, в ресурсы не влезут.

Суть приёма состоит в следующем, объявляется класс с единственным private статическим членом – нашей переменной. А функции, из которой к это переменной должен быть доступ, объявляются «друзьями класса».

Такой подход не привносит никаких дополнительных расходов по сравнению с объявлением глобальной переменной – никаких, ни на байт, ни на такт. А областью видимости позволяет управлять на уровне функций.

Пример:

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

// Объявляем
class _counter {
	static int __counter;
	friend void f1(void);
	friend void f3(void);
};
int _counter::__counter = 321;
#define counter _counter::__counter


void f1(void) {
	Serial << counter;// Нормально, т.к. f1 - друг класса
}

void f2(void) {
	Serial << counter;// Компилятор ругнётся, т.к. f2 - НЕ друг класса
}

void f3(void) {
	Serial << counter;// Нормально, т.к. f3 - друг класса
}

void loop(void) {}
void setup(void) {
	Serial.begin(115200);
	//...
}

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

Продолжение о динамической памяти следует.

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

Я бы тут ещё добавил немного о расположении переменных в памяти. Статические переменные лежат в области переменных, локальные - в стеке. Да и о volatile, об оптимизации не заметил упоминания. О переменных с фиксированным адресом, о union...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

faeton пишет:

Я бы тут ещё добавил немного о расположении переменных в памяти. Статические переменные лежат в области переменных, локальные - в стеке. Да и о volatile, об оптимизации не заметил упоминания. О переменных с фиксированным адресом, о union...

Так, добавьте! Кто-то или что-то мешает? Только про стек не надо - это специальная статья про статическую память, что следует прямо из её названия. А про union, volatile и т.п. - добавляйте на здоровье.

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

ЕвгенийП пишет:

faeton пишет:

Я бы тут ещё добавил немного о расположении переменных в памяти. Статические переменные лежат в области переменных, локальные - в стеке. Да и о volatile, об оптимизации не заметил упоминания. О переменных с фиксированным адресом, о union...

Так, добавьте! Кто-то или что-то мешает? Только про стек не надо - это специальная статья про статическую память, что следует прямо из её названия. А про union, volatile и т.п. - добавляйте на здоровье.

Вы, вроде, автор - начнёте ругаться, что я свой нос везде сую, да и лучше меня знаете С во всех его тонкостях. Но Вы тут немного запутали... Было бы лучше, на мой вгляд, начать вообще с понятия переменной, области видимости, а уже потом о различных вариантах. И немного смущает "статическая память", да и стековые называть динамическими... Для меня более понятно глобальные, постоянные/статические, локальные, параметры функций, динамические, свойства классов - в одной только цепочке описания уже смысл иерархичекий просматривается. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

faeton пишет:

Было бы лучше, на мой вгляд, начать вообще с понятия переменной, 

Вообще-то, это не начало, а продолжение. Это можно было понять из первой фразы, но Вы, видимо её пропустили. А вообще, если у Вас свой взгляд на то, как надо излагать материал - ещё раз повотряю, пишите, кто Вам не даёт? Открывайте свою тему и пишите на здоровье так, как считаете нужным. 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Вы действительно зря влезли в эту тему. Автор, как и в прошлый раз скопипастит из учебников общих мест, а разгребать "детали" и применение к микроконтроллерам, да конкретно 8-и битным, да с гарвардской архитекуторй - предложит кому-то иному... вот уже и вам предложил .. не удивительно ни разу. Вы перечитайте предыдущие ляпы, прибитые тут гвоздиками.. а ещё лучше найдите его любимое переопределение вывода в потоки .. это он сейчас, до темплейтов добрался.

В целом тема начата неверно изначально .. ибо "общие места из учебников" они больше к "общему программированию" .. вот и примеры исключительно на С++ .. к чему бы это? Думаете автор сам ТАК пишет? Да ни разу! Вот тут есть вполне вменяемый кусок авторского кода ..почти С++

Да, и по указанной автором ссылке, которую он КАТЕГОРИЧЕСКИ не рекомендует .. находим:

Разница:

1. в существенной для исполняемой части скетча экономии места на стеке, если не считать монстрообразный класс Serial, а только то что тут написано.

2. Необходимость писать вменяемый комментарий с использованием того или иного Документора. .. который вам ещё и документацию сделает автоматически.

Собственно и все "предубеждения" против глобалов.

А, учитывая, что у МК оперативной памяти "с гулькин нос" - использование глобалов - КАТЕГОРИЧЕСКИ рекомендуется. Вот, как раз ради ТАКОЙ экономии памяти и повышения скорости исполненния.

С чем автор благоразумно "там" спорить таки не стал .. :)

Предлагаю таки дождаться, когда автор продолжит тему в применении именно к AVR, с его особенностями передачи параметров промеж функций, опциями компилятора и их применимости и полезности в тех или иных случаях, ну и может быть пример по ссылке вовсе не зря "не рекомендован" и читатели таки смогут воочию увидеть КАК этот пример усыхает в требованиях к памяти, растет на глазах в скорости исполнения .. или достигает каких-то вершин в РЕКОМЕНДУЕМЫХ подходах к созданию скетчей ..

Не, здря влезли.. стоит подождать.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

Я точно не хочу принимать участие в холиваре. Только одно замечание:

Архат! static, даже внутри функции, размещается не в стеке.

Нас ведь разные люди читают, зачем путать читателя, как бы Вы не относились к автору?

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ну, я же с этим не спорю ..  да и вообще ни про static ни про чего ещё и слова не отписал. Был приведен "категорически нерекомендуемый пример" .. где параметры заменены на глобалы .. мой, пример. Тот самый, который наглядно показывает почему тут такое "категорически рекомендуется", только и всего. В отличии от голословного и безаппеляционного заявления автора. :)

Правда надо отдать должное автору, он юлит как "уж на сковороде": то категорически не рекомендует, то внезапно соглашается и даже пишет "хоть как-то оправдано" .. цирк!

Да, советую ещё читателям почаще обращать внимание на "мелкие вставки" типа "Любое случайное изменение", "случайно изменить её где-нибудь в 500 строках.." и прочие обращения к "дебильности" будущих читателей. Напомню: программа ничего сама СЛУЧАЙНО не изменяет. Изменяется то, и только то, что указано в программе автором. Отсюда: у вас, как программистов всегда есть выбор:

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

Или доверить компилятору натыкать кучу кода помимо вашего желания, прикрывающего ваше НЕЖЕЛАНИЕ учиться.

Только и всего, что всегда подстрочно утверждается в таких "оговорках". Как только слышите, читаете про "легко ошибиться, поэтому компилятор что-то делает сам" или "легко изменить где-то ещё, поэтому не рекомендуется" .. так и знайте: вас держат за баранов, не способных написать работающий код иначе.

Logik
Offline
Зарегистрирован: 05.08.2014

wdrakula пишет:

Я точно не хочу принимать участие в холиваре. Только одно замечание:

Холивар - так холивар, мой контракт с продавцами попкорна пока актуален.

wdrakula пишет:

Архат! static, даже внутри функции, размещается не в стеке.

А он это дето утверждает? Там правда ход мысли путан как его код, но я такого не заметил.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Arhat109-2 пишет:

его любимое переопределение вывода в потоки

А чего его искать? Вот оно.

template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

И какие у Вас к нему претензии? Он что, занимает на байт больше, чем простой вызов print? Или беда данного кода в том, что Ваш говнокод суперлибу не использует?

Arhat109-2 пишет:

Думаете автор сам ТАК пишет? Да ни разу! Вот тут есть вполне вменяемый кусок авторского кода ..почти С++

Да, неужели?! Вообще-то в том топике я явно написал, что код не мой, но Вы ведь не читаете, что Вам пишут – у Вас другие цели и задачи. Впрочем, тот код писал профессионал, потому, от arhat.h он отличается разительно. Тут я согласен.

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

Arhat109-2 пишет:

А, учитывая, что у МК оперативной памяти "с гулькин нос" - использование глобалов - КАТЕГОРИЧЕСКИ рекомендуется. Вот, как раз ради ТАКОЙ экономии памяти и повышения скорости исполненния.

С чем автор благоразумно "там" спорить таки не стал .. :) 

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

Да, и кстати, кто-то обещал не комментировать мои темы? Сбрехал, как всегда?

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

Arhat109-2 пишет:

А, учитывая, что у МК оперативной памяти "с гулькин нос" - использование глобалов - КАТЕГОРИЧЕСКИ рекомендуется. Вот, как раз ради ТАКОЙ экономии памяти и повышения скорости исполненния.

Такое впечатление, что Вы всё о тинках думаете. :) 32кб у 2560 (а я именно этот МК считаю чем-то существенным, способным что-то делать толковое, а не хвостом махать на переферии) это уже довольно неплохой объём памяти для одной бисинки. Если знать что и куда компилятор пихает в каких ситуациях, то вполне можно многое ему доверить. Когда я посмотрел код, сгенерированный ТурбоПаскалем - красивый такой, с явным академическим подходом и, в то же время, с трюками по нужде - внушился качеством работы программистов Борланда и на асме писал только резидентов и драйверы потом.

Что касается темы, многим тут оно пригодится, ибо не промышлкнную аппаратуру люди делают, а развлекаются и не черпают тут базовые основы программирования как профессию, надеюсь. 

Для рунета вообще характерное явление: пересказ своими словами того, что уже было красиво сказано академическим языком. Вы посмотрите, например, уроки по фотографии: пересказ инструкции к фотоаппарату сплошняком, но всё с таким пафосом... И импортные фотографы без понтов всегда расскажут интересное. :)

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

ЕвгенийП пишет:

faeton пишет:

Было бы лучше, на мой вгляд, начать вообще с понятия переменной, 

Вообще-то, это не начало, а продолжение. Это можно было понять из первой фразы, но Вы, видимо её пропустили. А вообще, если у Вас свой взгляд на то, как надо излагать материал - ещё раз повотряю, пишите, кто Вам не даёт? Открывайте свою тему и пишите на здоровье так, как считаете нужным. 

Простите, действительно не заметил начало. Слов много в тексте, читал бегло. :)

alexvs
Offline
Зарегистрирован: 22.07.2014

faeton пишет:

Такое впечатление, что Вы всё о тинках думаете. :) 32кб у 2560 (а я именно этот МК считаю чем-то существенным, способным что-то делать толковое, а не хвостом махать на переферии) это уже довольно неплохой объём памяти для одной бисинки. 

У ATMega2560 и 32кб? Это что то новое!

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

faeton, Вообще-то, даташит утверждает что у ATmega2560 всего 8килов оперативы на .. 128килослов программной памяти. Что по всем меркам соотношения размеров данных и программ явно недостаточно.

32 кила есть только у одной-единственной модели Х-мег, которые не используются в среде Ардуино и вовсе никак, в силу их ИНОГО подхода к программированию периферии, хотя и более удобного. Wiring переписывать надо и практически весь полностью..

Я ваще, в смысле в целом и практически всегда думаю о том, что заставлять комп выполнять ЛИШНИЕ операции - это означает: а) удорожать железо; б) переплачивать за свет; в) терять время в ожидании результата; и г) признать себя дебилом, неспособным написать правильный, короткий И шустрый алгоритм.

В порядке нарастания важности. Вы думаете почто всякие "евгенииП" тут так обостряются про мою писанину? По факту наличия ПОСЛЕДНЕГО пункта в КАЖДОЙ голове КАЖДОГО автора подсознательно. Это - автоматика, то бишь "внутричерепное ПЗУ". :)

alexvs
Offline
Зарегистрирован: 22.07.2014

Arhat109-2 пишет:

faeton, Вообще-то, даташит утверждает что у ATmega2560 всего 8килов оперативы на .. 128килослов 

Вообще то, уважаемый, если уж открыть даташит, то у ATmega2560 256Кб флэш памяти :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Верно, 256 килоБАЙТ, но каждая команда имеет 2 байт или 1 СЛОВО .. итого имеем .. 128 килослов. Нет никакого смысла измерять флеш байтами. Что я "не так написал"? :)

Клапауций 232
Offline
Зарегистрирован: 05.04.2016

alexvs пишет:

У ATMega2560 и 32кб? Это что то новое!

да, ладно - пора бы уже перстать удивляться ляпам faeton'а.

faeton у нас - сферический программист в аппаратном вакууме.

alexvs
Offline
Зарегистрирован: 22.07.2014

Вы хитрите, в даташитах ничего про смысл измерений в байтах, либо в каких то других единицах, ничего не сказано. Там сухие цифры.

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

ЗЫ Я не пытаюсь Вас уличить, я просто Вас поправил.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

ЕвгенийП, о да .. приношу свои извинения, забыл что и даже этот код НЕ ВАШ, а написан Вам по вашей ПРОСЬБЕ, за которую вы оказывается "надрали зад автору" (видимо от непонимания, что проверку параметров ВСЕГДА должен делать вызывающий автор, а не автор вызываемых функций должен предполагать в каждом программисте идиота, а не специалиста) .. видел, запамятовал .. старею видимо, склероз однако.

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

Впрочем, всем это уже и так ясно.

Всё, извиняюсь что встрял в ваши копипасты учебников (кстати, темплейт принта тоже находил в одном из них и практически в том же самом виде, это тоже "не ваш код" :) ) .. жду детализации применения прописных истин к конкретно AVR архитектуре с учетом особенностей компиляторов и подходам И способам "автоматического" писания быстрых, шустрых, компактных и ясных программ конкретно для Ардуино, а не "сферических коней учебников". Заодно и может дождусь разъяснения почему глобалы (да хоть бы и static) так "категорически не рекомендуются" вами и считаются "безграмотными". А то одно сплошное бабольство, что не удивительно уже ни разу.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Лень сейчас лезть в даташит .. откройте для себя раздел "самопрограммирования" и описание процесса адресации флеша, особенно в разделе больше 64к адресов .. и обнаружьте там то, что мною написано тут .. даже АТмел про это пишет вполне спокойно. 256килоБАЙТ - всего лишь "маркетинговый ход".

alexvs
Offline
Зарегистрирован: 22.07.2014

Вы про это? Соглашусь с Вами, ну и с ними тоже. Хотя на видном месте даташита красуется гордо 256кБ.

Даже если вы про другую часть описания - я все понял и так.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Угу. Там ещё есть про особенности работы с младшим битом адреса флеш при записи в старшие 64 килослова, сильно не вникал за ненадобностью. Просто отметил про себя, что при случае надо будет перевести вдумчиво.

faeton
faeton аватар
Offline
Зарегистрирован: 21.03.2016

alexvs пишет:

faeton пишет:

Такое впечатление, что Вы всё о тинках думаете. :) 32кб у 2560 (а я именно этот МК считаю чем-то существенным, способным что-то делать толковое, а не хвостом махать на переферии) это уже довольно неплохой объём памяти для одной бисинки. 

У ATMega2560 и 32кб? Это что то новое!

Ошибся. Наросились сразу... Нет подумать, что мега с внешней памятью была мной упомянута. :)))

arduino328
Offline
Зарегистрирован: 01.09.2016

А когда будет давно обещанный третий этюд про память?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Как руки дойдут.

А некоторых, кто собрался тему про брейнфак засирать холиваром персонально от того этюда забаню :)

ptr
Offline
Зарегистрирован: 28.05.2016

ЕвгенийП, зря про модификатор const ... PROGMEM не упомянули.

В случае, если нам нужна скалярная константа, то лучше использовать #define. То есть, вместо

const float pi = 3.141593;

следует писать:

#define PI  3.141593;

А вот если нам нужен целый массив, то ограничиваться только указанием const для массива констант не всегда правильно. Для Ардуино можно явно указать, что данный массив должен располагаться во флеше, а не в оперативной памяти, которой и так мало. Для этого служит модификатор описания переменной PROGMEM. Например:

const int some_array[] PROGMEM = { 735, 1024, 2022 };

Альтернативная запись:

const PROGMEM int some_array[] = { 735, 1024, 2022 };

Естественно, если Ваш проект наоборот, ограничен в программной памяти (флеше), но не в оперативной, модификатор PROGMEM не нужен.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

В случае, если нам нужна скалярная константа, то лучше использовать #define. То есть, вместо

const float pi = 3.141593;

следует писать:

#define PI  3.141593;

В данной среде исполнения (данный компилятор и опциями, установленными в IDE) это почти всё равно - скалярная константа почти никогда не располагается в памяти, а подставляется точно также, как из #define. Посмотрите на ассемблерный код, выдаваемы компилятором.

Но запись через констану лучше тем, что сохраняется полный контроль типов, который теряется при #define (или уж надо в #defiine заодно и тпи определять).

ptr
Offline
Зарегистрирован: 28.05.2016

ЕвгенийП пишет:

Но запись через констану лучше тем, что сохраняется полный контроль типов, который теряется при #define (или уж надо в #defiine заодно и тпи определять).

А кто мешает в #define явно указать тип? В примере явный float.

12 - int
12L - long

Ну или правоверным путем:

INT8_C(c)
INT16_C(c)
INT32_C(c)
INT64_C(c)
UINT8_C(c)
UINT16_C(c)
UINT32_C(c)
UINT64_C(c)
INTMAX_C(c)
UINTMAX_C(c)

 

ptr
Offline
Зарегистрирован: 28.05.2016

ЕвгенийП пишет:

В данной среде исполнения (данный компилятор и опциями, установленными в IDE) это почти всё равно - скалярная константа почти никогда не располагается в памяти, а подставляется точно также, как из #define. Посмотрите на ассемблерный код, выдаваемы компилятором.

Посмотрел.

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

Причем именно в памяти. Во флеш он их засунет только если явно указать PROGMEM.

Так что, в общем случае, define заметно лучше, чем const.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

ЕвгенийП пишет:

(или уж надо в #defiine заодно и тпи определять).

А кто мешает в #define явно указать тип? В примере явный float.

Ничто не мешает, я ж там явно это написал.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

Если проект не в одном файле ...

Это явный оффтопик в данном цикле этюдов :)))  

Ибо в самом начале было прямо заявлено: "Про такие переменные я говорить не буду, т.к. они реально нужны только в больших «многофайловых» проектах, а те, кто пишут такие проекты не являются целевой аудиторией моих этюдов".

ptr
Offline
Зарегистрирован: 28.05.2016

Кому как. Я так леплю проект из нескольких файлов, даже когда он небольшой.

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

 Я так леплю проект из нескольких файлов, даже когда он небольшой.

Я тоже. Не люблю больших файлов. Значит, мы с Вами "не являемся целевой аудиторией моих этюдов" :)))

Если коротко - тем, кто умеют делать проекты в куче файлов мои этюды не нужны.

ptr
Offline
Зарегистрирован: 28.05.2016

ЕвгенийП пишет:

Если коротко - тем, кто умеют делать проекты в куче файлов мои этюды не нужны.

А я прочитал. С интересом. И благодарен Вам за этот этюд. Хотя на С программирую профессионально, методикой преподавания владею плохо, а у Вас с этим явно лучше, чем у меня.

P.S. Про const ... PROGMEM скопировали бы в этюд, раз возражений нет.

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

ЕвгенийП пишет:

Если коротко - тем, кто умеют делать проекты в куче файлов мои этюды не нужны.

А вот и фиг!

Я делаю многофайловые проекты. Этюды мне нужны.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

ptr пишет:
ЕвгенийП пишет:
В данной среде исполнения (данный компилятор и опциями, установленными в IDE) это почти всё равно - скалярная константа почти никогда не располагается в памяти, а подставляется точно также, как из #define. Посмотрите на ассемблерный код, выдаваемы компилятором.

Посмотрел. Если проект не в одном файле, ... компилятор честно полезет за ними в память. ... Так что, в общем случае, define заметно лучше, чем const.

Верно. Если перечитаете все прибитые тут опусы этого автора, то поймете что выделенное в его цитате - есть главная идея этого автора. Всё что тут "рекомендуется" почерпнуто из учебников "общего" курса программирования и крайне мало коррелирует с особенностями разработок под микроконтроллеры.

Блин, зарекся уже тыкать этому автору в его "копроподход" к программированию, но темы прибиты гвоздиками и поэтому постоянно "всплывают" и непонимание их применимости не снижается ни разу.

P.S. Не будет этот автор ничего править в своих "уроках", поверьте уж. :(

P.P.S. По прежнему "жду детализации применения прописных истин к конкретно AVR архитектуре с учетом особенностей компиляторов и подходам И способам "автоматического" писания быстрых, шустрых, компактных и ясных программ конкретно для Ардуино, а не "сферических коней учебников". Заодно и может дождусь разъяснения почему глобалы (да хоть бы и static) так "категорически не рекомендуются" вами и считаются "безграмотными"."

.. прошел уже год. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Архат, мне казалось, что когда Вы в очередной раз, сидя в луже, что-то блеяли про "ошибки в языке", мы заключили перемирие. Обратите внимание, я его не нарушаю. Вы хотите снова сидеть в луже на виду у всего форума? 

Предлагаю Вам удалить Ваш пост (тогда я сразу же удалю этот) и продлить перемирие.

ptr
Offline
Зарегистрирован: 28.05.2016

Ну глобальные переменные никто не любит именно за то, что они глобальные.

1. Подавляющее большинство проектов выполняется группой людей. Причем не редко удаленных друг от друга. Компилятор знает о глобальной переменной проекта только то, что он должен предоставить линкеру ссылку на нее для связывания. Где и как эта переменная используется - ему не ведомо. А значит, если два программиста заведут у себя глобальную переменную с одним и тем же именем, они долго будут потом разбраться, почему значение этой переменной непредсказуемо меняется.

2. Даже на AVR вполне можно написать программу, выполняющую асинхронно нескольго задач. А функция, использующая глобальную или статическую переменную может быть использована только в одном потоке, не не в двух и более. Поэтому использование статики не рекомендуется. Именно в угоду многопоточности.

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

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ваше блеяние и мое якобы сидение в луже - есть только И только плод вашей "больной фантазии". Ваше сидение в лужах прибито тут гвоздями и никакого перемерия (а равно как и вражды) я с Вами НЕ заключал. Что и когда там "показалось" вашей больной фантазии (перемирие али война) - это исключительно ВАШИ ПГОБЛЕМЫ.

Мне просто надоело вас исправлять и я перестал писать в разделе "программирование". Тем более, что Вы дали тут кучу РАЗВЕСИСТЫХ обещаний .. так что "ждемс":

1. Разъяснение по вопросу статических переменных .. чем оно таки ХУЖЕ и без привлечения высказываний типа "легко ошибиться" .. не стоит априори ВСЕХ разработчиков считать за ДУРАКОВ (и расписывать себя в этом АВТОМАТИЧЕСКИ).

2. Супер-класс по работе с пинами (вы там челу обещали "как у вас гости уедут.." помнится)  .. до сих пор "гостят", да? С полгода прошло уже пожалуй ..

Ещё таки надеюсь увидеть Ваш СОБСТВЕННЫЙ код, а не ваших студентов или стыренный с учебников.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

ptr, Я вот очень даже люблю и пользую глобалы во многих своих проектах. Их достоинства - это краткость и компактность кода И прямое повышение скорости его исполнения, хотя бы за счет уничтожения передачи параметров промеж вызовов. Да, кстати, почти половина моего RTOS для AVR работает исключительно на и за счет глобалов .. а вот обоснований "против" так никто и НЕ ПРИВЕЛ.

.. я просто неприемлю, когда авторы "рекомендаций" того или иного толка считают априори своих читателей за ДЕБИЛОВ, аргументируя "легко ошибиться", "незаметить" и т.п. В мире существует ТУЧА способов избегать таковых ошибок "по недосмотру", начиная от способов документирования кода и заканчивая "умными редакторами с ЯВУ"..

ptr
Offline
Зарегистрирован: 28.05.2016

Arhat109-2 пишет:

1. Разъяснение по вопросу статических переменных .. чем оно таки ХУЖЕ

Я объяснил выше. Средства С не позволяют изолировать статические переменные между потоками в многопоточном приложении.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

Я объяснил выше. Средства С не позволяют изолировать статические переменные между потоками в многопоточном приложении.

Напрасно Вы что-то объясняете этому полу- (или четверть-) грамотному "кодеру прог".  Это безграмотное существо с необычайным самомнением. Когда Вы ткнёте его носом в очевидный бред и подкрепите это проверочным кодом, в ответ получите - "это косяк компилятора", а когда Вы не поленитесь и покажете ему место в стандарте языка, из которо следует. что компилятор прав, в ответ получите "это косяк языка". Вот и всё. У Архата ошибок не бывает - бывают только косяки компилятора и/или языка. Имейте это в виду и не тратье время, если, конечно, Вы не хотите этим поразвлекаться.

Я бы не поверил в то, что я сейчас написал (что такие "спецы" бывают), потому допускаю, что и Вы не поверите. Если хотите, могу найти ссылки на "косяки компилятора и языка".

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Архат, я Вас понял, спасибо за разъяснения.

Хочу лишь отметить, что лично Вам я ничего не обещал, а аргументы об опасностях работы с глобальными переменными даны в этой теме. Если Вы их не понимаете - ну, не всем дано.

rhat109-2 пишет:

 не стоит априори ВСЕХ разработчиков считать за ДУРАКОВ

На этом форуме я считаю таковыми отнюдь не всех, а только одного.

И ещё, я как-то Вам уже советовал, сделаю эо ещё раз. Вы можете наскакивать на меня с общими словами, что я дурак, ничего не понимаю и т.п., но без кокнретики - без примеров кода. Всякий раз, когда Вы налетали на меня с примерами кода, Вам приходилось потом блеять про "косяки компилятора и языка", помните? Так будет и впредь - имейте это в виду и постарайтесь воздерживаться от конкретики. Это так, добрый совет.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

А зачем их надо изолировать между потоками при программировании МК? У него памяти с "гулькин нос", как оперативной так и программной .. никаких "особо сложных" проектов не сделать "по определению" .. а вот задача экономии памяти (в частности стека) стоит вполне себе актуально.

Как пример, в опубликованной части arhat.h есть обработчик ультразвукового датчика HCSR-04 .. на прерываниях, то есть "отдельный поток" и работает он .. через глобал, оставляя результат замера в этом самом глобале. Точность измерения расстояния - миллиметры, часто 1мм. Скорость замеров - предельно возможная, с "главным циклом" он никак не связан .. и? Зачем мне изолировать структуру его глобала от главной программы, тем более что результат измерений она смотрит согласно флагу состояния датчика в этой структуре и результат измерений берет из того жа самого глобала, когда он действителен и новый?

И так у меня работают практически ВСЕ датчики и энкодеры, и, если требуется, то и через "таймерный хук" любимого millis(), в частности замер цвета и освещенности от TSC3200. И "главная программа" тоже может состоять из нескольких процессов, в т.ч. и с реализацией межзадачного взаимодействия по стандарту Ада (восстановил себе свою С-библиотеку этого стандарта межзадачного взаимодействия).

Даже у меги2560 всего 128килослов флеша .. это что, реально "большой проект"?!? :)

Тут вот не так давно, высказывался что Мега2560 вполне способна тянуть управление около сотней периферийных устройств в цикле с 1мсек периодом .. мне ответили что "это весьма непросто" .. конечно не просто, если глобалами не пользоваться, а использовать С++ с виртуальным наследием .. ваще "реакцию" системы довести до 1-10сек очень даже реально .. что тут и видм регулярно. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

"На этом форуме я считаю таковыми отнюдь не всех, а только одного." - э-э-э .. себя? Весьма похвально.

ptr
Offline
Зарегистрирован: 28.05.2016

Arhat109-2 пишет:

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

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

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

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

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

ptr пишет:

и PM, и постановщик, и архитектор, и программист, и тестировщик

Просто "прогер". Этим всё сказано.

ptr
Offline
Зарегистрирован: 28.05.2016

Arhat109-2 пишет:

А зачем их надо изолировать между потоками при программировании МК?

Потому что в разных потоках, в общем случае, переменные должны принимать разные значения. А статическая переменная на это не способна. А программист не способен постоянно безошибочно контролировать, какая функция TS (Trhread Safe), а какая нет. И ошибки такие ловятся очень долго.

Попробуйте попользоваться стандартной функцией strtok() в многопоточном приложении. Получите массу приятных неожиданностей )

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

ptr, писал: "Во-первых, большинство архитектур поддерживают .."

Ну вот дальше можно и не писать ничего вовсе. Тут - форум за Ардуино, который на базе AVR, который .. ничего такого не поддерживает. Я как раз за ЭТО И ВОЗМУЩАЮСЬ в перлах автора этой темы: ТУПОЙ И ПРЯМОЙ перенос способов программирования ОБЩЕГО НАЗНАЧЕНИЯ на конкретно ЭТУ архитектуру и программирование микроконтроллеров в целом.

Сие есть говнокод, говноподход недопустимый при работе с микроконтроллерами и этому и обучают темы автора, прибитые гвоздиком, как понимаю им самолично. В любом ином месте - "да пожалуйста".

И ни в одной теме нет ни единого упоминания за особенности программирования микроконтроллеров. Перечитайте. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Евгений, Вас снова поперло на оскорбления .. что снова свидетельствет о том, что внятных разъяснений, кроме "дуракам не положено" так и не последует. Зачел. :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

ptr .. и снова вы сваливаетесь на "программист === дурак": "А программист не способен постоянно безошибочно контролировать, какая функция TS (Trhread Safe), а какая нет." Напомню постулат из психологии: "Каждый всегда судит о других по себе любимому"...

В большом приложении - да, это "сложно", если не принимать специальных мер. Но, ещё раз! Тут программирование микроконтроллеров! Ну не бывает тут "больших приложений"! :)