Pull to refresh

Comments 74

Перпеключаем светодиод, если был ноль то будет 0 и наоброт
ну ну…
запустить Make утилиту.

Оформление makefile'а странноватое. Как минимум, не хватает списка исходников в отдельной переменной и цели all:
Еще хорошо бы добавить цель prog: или подобную, для прошивки. Например, у меня для stm32f103 это выглядит так:
prog:
	stm32flash -w main.bin /dev/ttyUSB0
prog2:
	openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "init" -c "reset halt" -c "flash write_image erase main.bin 0x08000000" -c "reset run" -c "exit"

И для stm32L151:
prog: $(frmname).bin
	openocd -f interface/stlink-v2.cfg -f target/stm32l1.cfg -c "init" -c "reset halt" -c "flash write_image erase "$(frmname).bin" 0x08000000" -c "reset run" -c "exit"
prog2: $(frmname).bin
	-stm32flash -j /dev/ttyUSB0
	sleep 1
	-stm32flash -k /dev/ttyUSB0
	sleep 1
	-stm32flash -e 0 -w $(frmname).bin /dev/ttyUSB0

Чтобы сразу в консоли вбивать
$ make
$ make prog

//Включаем тактирование GPIO I порта, порт номер 8 (очень важно)
RCC->AHB1ENR |= (1<<8);
Магические чиселки — плохая идея. Лучше было объявить макроконстанту #define RCC_AHBENR_GPIOIEN (1<<8) и пользовались ей.
//Далее настраиваем режим ножки на «Выход»
GPIOI->MODER |= (1<<2);

//Теперь указываем тип выхода, в данном примере, push-pull, необходимости указывать нет.
//По умолчанию стоит push pull
GPIOI->OTYPER &= ~(1<<1);

//Скорость с которой будет происходить переключение ножки, очень важный парметр при работе с переферией
GPIOI->OSPEEDR |= (2<<2);

Без описания формата регистров вообще непонятно почему в одном случае сдвигаем на 1 бит, а в другом — на два. Стоило упомянуть, что регистр «режим работы порта» хранит по два бита на порт с такими-то комбинациями, поэтому для настройки i=го вывода используем конструкцию GPIOI->MODER |= (0b01 << (2*i) );
А вот OTYPE'у нужен только один бит, поэтому умножение на 2 не нужно
На счет портов лично я бы поскорее перешел к макросам вроде вот таких
#define LED_RED D,1,1
GPIO_config( LED_RED );
GPIO_ON( LED_RED );
GPIO_OFF( LED_RED );

Кажется, свою реализацию для STM я в общий доступ не выкладывал, но если надо…
Без описания формата регистров вообще непонятно почему в одном случае сдвигаем на 1 бит, а в другом — на два.
тут людям надо учится мануал листать. А насчет почему не использовал GPIOI->MODER |= (0b01 << (2*i) ); то это в следующей статье я пройдусь, там будет более гибкая реализация драйвера порта
Эх, прямо по больному :) На счет мануалов.
Но все же читатель не должен угадывать, почему вы написали именно так, а не иначе. А запись со сдвигами то на 1, то на 2 все же не очевидна. Опять же, по портам как раз статей немало, можно было и ссылку дать.
Учту в следующей статье, а эту долню обязательно.
кстати openocd ругался на stlinkv2 (deprecated) поэтому я перешел на stlink.cfg
Макрос _VAL2FLD() — снижает количество головной боли.
Едва ли он поможет против наркоманского распределения битов по регистрам у STM. Два примера, которые мне сильнее всего поломали мозг (уверен, не последние!):
1. Порты в stm32F103, когда порты 0-7 располагаются в CPIOx->CRL, а 8-15 в GPIOx->CRH. Заворачивать это в человеческие макросы было непросто.
2. Последовательность измерений АЦП, когда 28 элементов очереди разбиты по 5 регистрам, причем блоками по 5 бит масками вида #define ADC_SQR2_SQ22 ((uint32_t)0x000F8000). Пришлось писать макрос, который бы стирал регистр по маске (это как раз несложно) а потом сдвигал аргумент на нужное количество бит.
#define PM_BITMASK(reg, mask, val) do{ reg = (reg &~mask) | (val * (mask &~(mask<<1)) ); }while(0)

(Ахтунг! Писал из головы)
Разработка под Arduino

Это шутка или я чего-то не знаю?)
Atmel SAM3X8E, ядро cortex m3, так что ты чего то не знаешь.
Но автор-то про STM32F7 рассказывает. Причем отнюдь не в контексте Ардуины.
Спишем на то, что он по-тихому просвещает ардуинщиков. Они ищут по тегам «Ардуину» и вдруг наткнутся на эту статью — глядишь и пробьют оболочку.
По идеи знания и там применить можно. хотя структура перефирии там другая. надо бы опробовать. А насчет ардуины, кто то же должен заниматься пропогандой
Для начала — отличный цикл. Только местами побит слишком мелко. В любом случае — продолжайте. Это важно и интересно.

Теперь к сути. Вопрос. Почему
typedef struct
{
  ...
  volatile unsigned int DCKCFGR2;     
} RCC_Struct;

И при этом
typedef struct
{
  ...
  volatile uint32_t AFR[2];  
} GPIO_Struct;


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

И еще. Большинство компиляторов пректрастно отнесется к замене
typedef struct
{
  ...
  unsigned int          RESERVED0;    
 ...
  unsigned int          RESERVED1[2]; 
 ...
  unsigned int          RESERVED2;    
 ...
} RCC_Struct;

на
typedef struct
{
  ...
  uint32_t          :32;
 ...
  uint64_t          :64; 
 ...
  uint32_t          :32;   
 ...
} RCC_Struct;


Битовые поля давно в стандарте и совершенно напрасно игнорируются разработчиками BSP от чипов. Они сильно упрощают написание и понмание кода. Раз уж взялись делать сразу и хорошо может стоит присмотреться к ним? А union позволит обращаться к регистру в целом и к отдельным битам как
BLOCK->REG.val = 0x12345678;
BLOCK->REG.bit.STATUS = 0;

И отдать битовые операции на откуп компилятору.
Инты поправил. Насчет битовых полей, я не уверен, но думаю скомилирует он конечно также как и без битовых полей. Я как то не особо вглядывался в оптимизацию структур, но думаю что при компиляции всеравно будет использован хотя бы байт. Тоесть тип будет заменен на полный. Так что тут дело вкуса нверно. А так палец вверх за полезный совет. Изучим вопрос.
Хм… Я имел в виду ровно противоположное. Компилятор знает размерности типов, и имеет их описание в stdint.h. По мне правильным решением было бы использовать uint32_t. И, конечно, не надо делать то, что уже сделано.

По битовым полям. В целом, решение совсем не однозначное. С одной стороны оно сильно упрощает востриятие кода. Смотрите сами разницу
if (BLOCK->STATUS.bit.READY) {
...
   BLOCK->STATUS.bit.READY = 0;
}
vs
if ( BLOCK->STATUS && (1<<READY)) {
   BLOCK->STATUS &= ~(1<<READY);
}


С другой стороны множественная установка битов все равно будет требовать либо обращения ко всему регистру с «магическими константами», либо последовательности операций (и, соответственно, потери скорости), либо надежды на компилятор, который поймет и оптимизирует (конечно, усложнив отладку). Пример
BLOCK->REG.val = 0x800000001; /* Magic constant */
vs
BLOCK->REG.val = (1<<FIRST | 1<<LAST); /* Mixed style */
vs
/* bits style */
BLOCK->REG.val = 0; /* reset */
BLOCK->REG.bit.FIRST = 1;
BLOCK->REG.bit.LAST = 1;


Что применять вопрос открытый. Но чаще всего нет необходимости установки двух-трех битов одновременно. Или сброс всего регистра или проверка (установка, сброс) отдельных бит. Так что это потенциальная возможность упростить понимание кода, но, безусловно, не «волшебная пилюля».
Инты поправил
Может, это у меня не обновилась страница, но сейчас там unsigned int и тому подобные типы негарантированного размера. Это вы не везде поправили на int32_t и подобные, да?
Насчет битовых полей, я не уверен
Не стоит. По крайней мере при взаимодействии с системными файлами. Насколько я надеюсь, вы хотите привести свой «заготовочный» код к стандартному ST-шному. А они битовыми полями не пользуются. То есть если вы сразу жестко на битовые поля завяжетесь, потом придется портировать не только основной код, но и заголовочники.
Даже если гарантируется, есть два соображения против:
Типы фиксированного размера вроде int16_t проще для восприятия человеком, не надо вспоминать какой же размер int на вот этой архитектуре.
В заголовочных файлах самих ST используются именно типы фиксированного размера. Если будете приводить свой код к стандартному, автозамена по коду будет неприятным этапом.
Кстати, если не будете, можно попробовать наоборот, поиграться с битовыми полями. Вдруг да удобнее получится
А вот зачем использовать для регистров фиксированного размера обычные типы данных из С89, я причин не вижу.
Не передивайте, я добавлю типы чуть позже. И я полностью согласен что они лучше читаются. Но по факту это тот же typedef unsigned int int32_t.
Крайне интересный вопрос на самом деле.

Типы в языке С написаны для переносимости. Т.е. однажды написанный исходник должен компилироваться на любой платформе. Но при этом никто не гарантировал, что размерность стандартных типов будет сохраняться. Да она и не сохраняется. До поры это было на важно — переход 16 -> 32 бита прошел удивительно гладко. И даже рекомендовали использовать стандартные типы, мотивируя это тем, что на более крутых системах вы автоматически получите большие диапазоны значений.

Но потом случился момент, конда одновременно в природе сосуществовали 16, 32 и 64-битные системы. А еще каждая из них может быть «тупо-» или «осторо-» конечной. Т.е. BigEndian (BE) и Little-Endian (LE). И тут стало понятно, что с этим надо что-то делать. Первой ласточкой стал stdint.h, позволяющий четко описывать размерность данных. Потом комплект макросов cpu_to_be/cpu_to_le. В ARM для этого есть инструкции REV* (REVSH например). И, наконец, шикарная реализация в Linux типов be16/le16 им подобных. Последнее очень бы хотелось видеть в стандарте языка, но… Не все так просто.
Насколько я понимаю, идея не в том, что «получите больший диапазон значений», а в том, что чем больше разрядность машины, тем более она мощная, тем больше у нее памяти и тем больше нагрузку ей дают. Соответственно, и размер указателя будет больше, и размер массивов для обработки будет больше.
А вообще, по-хорошему, стоит использовать даже не uint32_t, а uint_fast32_t. То есть если работа с 64-битным числом будет быстрее, подставят именно его. А если какое-нибудь 38.5-битное — то его.
А «остроконечность» вообще определяется железом, не всегда его можно безболезненно переключить.
А такие «внезапные» просадки производительности на ровном месте все-таки не в стиле Си. Поэтому явное указание endian'а могут и не завезти.
Идея как раз и была в том что бы можно было получить больший диапозон значений. Фишка в том что 8 битный мк дольше будет выполнять операцию с 32 битным числом чем мк с 32 битными регистрами и 32 битной шиной. И так далее с паматью и указателями. Даже если извратиться, на AVR помойму есть 16/32 битный регистр или набор из 2х 8 битных специально для указательных функций. На этом примере, ты просто дольше будешь получать доступ к данным в памяти, пока запихаешь один регистр потом другой. Вообщем дело как раз таки получить больший диапазон значений одним регистром и одной операцией.
Про диапазон сам по себе — сомнительно. Мы же говорим про переносимость, то есть один и тот же код на разном железе. Скажем, цикл от 0 до 100000 на 32-битной машине сработает нормально, а не 8-битной — нет. Но вот если это проход по массиву, размер которого определяется скоростью ядра, уже больше похоже на правду.
У AVR это регистры X(r26:r27), Y(r28:r29), Z(r30:r31), но специальной арифметики для них почти нет, разве что adiw (прибавить константу от 0 до 63), sbiw (вычесть константу 0-63), movw (копирование) и кажется все. Если нужно хотя бы пройти по индексу — разбиваем адресную пару на отдельные регистры и работаем с ними как с обычными 8-битными.
Насчет переносимости кода, тут я дико протестую. Вот переносить например код с F4 на F7. Можно, люди делают, но по сути в этом мк код совсем по другому должен быть написан. есть новый функционал который влияет на всю работу системы. Код F4 заработает конечно, но не будет лучше кода написанного специально под F7
Тогда какое отношение имеет размер стандартных типов?
Незнаю, я считаю что это только для удобства. Вообще стараюсь иметь логисчкий тип для всего, хотя по сути все это просто инт(короткий или длинный). Не я начал тему про типы данных. Просто там ниже уже про переносимость с архитектур говорят. Вообщем порнография.
Просто там ниже уже про переносимость с архитектур говорят. Вообщем порнография.


Ой напрасно вы так. Особенно с свете вашего цикла статей.

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

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

В идеале любой код, не завязанный непосредственно на железо, должен быть переносим. При чем не только между контроллерами, но и между контроллерами и ПК. А по разнице производительности M0...M4F, AVR, PIC и прочим и оптимизации кода специально под них — вопрос открытый. Тут упоминались очень «вкусные» особенности AVR в части работы с битами. Но именно по причине их наличия и надо писать код так, чтобы компилятор мог без проблем их использовать (опять же — с любым уровнем оптимизации). А остальное на мой взгляд предельно просто — чем производительней контроллер, тем с большим количеством типовых задач одновременно он должен уметь работать. Вот, собственно, и все.
Первый абзац я непонял. Я ведь вроде сказал именно это? Или об этом подумал, а сказал что-то другое? При использовании стандартных типов размер массивов, а соответственно и индексов, при увеличении разрядности увеличиваются. Как и максимальные значения для арифметики и логики.

По Fast — все так. В той части, которая вычислительнее предпочтительнее fast. Для описания регистров — строгая размерность. Вообще стандарт языка вещь великая. Там много интересного написано. И любой уважающий себя разработчик с ним должен как минимум ознакомиться, а лучше внимательно изучить.

Остальное — это только мои внутренние мироощущения. Хотя, указание endian'а для переменной — это довольно хороший вариант для оптимизатора. Ему будет проще, чем пытаться оптимизировать каждый раз уникальный код пользователя. Особенно это становится важным, когда к данным типам начинают применяться те самые битовые поля. При этом итоговый результат сохраняется неизменным и не ломается совместимость с ранее написанным кодом. Впрочем, это хотелка. И шансы на ее реализацию я рассматриваю как весьма маловероятные. По самым разным причинам.
А вообще, по-хорошему, стоит использовать даже не uint32_t, а uint_fast32_t.

Не стоит. Потому что нужно именно 32 бита, не минимум 32 бита. Иначе у вас вся структура разъедется. По тем же причинам стоило бы вломить тем, кто вместо нового типа time64_t изменил разрядность time_t поломав двоичную совместимость, что напрочь убивает смысл использовать подобный тип.
А еще не надо забывать, что компилятор может поля структуры выровнять на границу слова, что бы доступ ускорить… Помню я тот давний зимний вечер, лет 20 назад, когда я узнал о существовании директивы #pragma pack. Потому что вместо заголовка bmp-файла считывалась какая-то лажа.
… вместо нового типа time64_t изменил разрядность time_t поломав двоичную совместимость...


А разве кто-то обещал двоичную совместимость? Обещали совметимость на уровне исходного кода — и она как раз выполнена.

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

На уровне исходного когда и с short/int/long все нормально. long t=time(NULL) перенесется ничуть не хуже, чем time_t t=time(NULL).
А смысл typedef — машинно-независимые типы.

Смысл typedef в возможности определить пользовательский тип. Почему поведение time_t должно отличаться от size_t? И что есть критерий нормальности?
Смысл typedef в возможности определить пользовательский тип.

во-первых typedef не определяет новые типы, а только синонимы к существующим, а во-вторых:
"… The first is to parameterize a program against portability problems. If typedefs are used for data types that may be machine-dependent, only the typedef s need change when the program is moved."

И что есть критерий нормальности?

Компилируется и работает. Или у вас есть другой критерий?
Да, так. Хорошо. Допустим. А ссылка на цитирование? А что если С99 6.7.1.3 или C17 6.7.1.5: «The typedef specifier is called a ‘‘storage-class specifier’’ for syntactic convenience only.» с последующим вопросом на тему что такое пользовательские типы для компилятора? Которому из источников будет больше веры?

И по сути. Да, для переносимости между платформати поменять надо только один typedef. Только что с size_t, что с time_t делать это должен исключительно разработчик компилятора. Никак не конечный пользователь, будь он даже системным программистом. Задумано так.

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

Во всяком случае мне кажется так. И пока ничего убедительного против такого подхода я не видел.
Которому из источников будет больше веры?

Моему. Потому что это K&R. Им виднее, что они придумали. :)

Да, для переносимости между платформати поменять надо только один typedef.

Именно. Вот только для чего его менять-то? Как думаете?

то правильнее этот тип выразить через typedef

time_t так и определен. Только для чего его определять, если на разных архитектурах он будет разный?

И пока ничего убедительного против такого подхода я не видел.

Ну тогда попробуйте убедить меня что time_t имеет преимущества перед long.
Потому что это K&R. Им виднее, что они придумали. :)


Ваше право. Я предпочитаю верить авторам стандарта. K&R там в авторитете, но и непререкаемыми авторитетами они не считаются.

попробуйте убедить меня что time_t имеет преимущества перед long.


Ну, вот это как раз совсем просто. Сколько запаса даст 64 битный time_t? Думаю, на три последовательно атомные войны хватит. С другой стороны, появление 128 битных машин лишь вопрос времени. При чем не столь отдаленного. А теперь попробуйте привести убедительные примеры того, что time_t на них должен будет стать 128 разрядным. А в случае с long именно так и должно бы произойти, не будь time_t определен через typedef.

P.S.

Подождите. Я, кажется, понял наконец о чем вы. Да, переход time_t c 32 на 64 бита прошел (проходит???) чуть менее гладко, нежели аналогичный переход для size_t. Но суть этих типов именно в том, что меняются они крайне редко. И именно за этим они вынесены typedef'ом. В то время как char, short, int и далее — очень сильно аппаратурно специфичны. А time64_t бесполезен чуть более, чем полностью. Причина — введи такой тип, и придется в libc держать дублирующийся фукнционал. Сигнатуры функций потихоньку начнут напоминать GL'евские. Мне (и авторам стандарта) такой вариант крафне не нравится. Впрочем — это ж GPL. Если кто хочет, то может попробовать завоевать мир и с таким подходом. Форк делается очень легко.
Я предпочитаю верить авторам стандарта.

Вот им-то я и предлагаю вломить :)

Сколько запаса даст 64 битный time_t?

Да приблизительно столько же, сколько 64-битный лонг. И уж точно 128-битный лонг не окажется хуже.
Это как-то вам поломает код? Или вам очень жалко лишних 4 байта?

Да, переход time_t c 32 на 64 бита прошел

Да дело не в переходе, а в том, что машинно-независимые типы стали машинно-зависимыми. У K&R про какой-то тип из *_t было пояснение что «на нашем компе это short int, а на ваше м.б. не так, поэтому лучше использовать typedef». И понятно, что задумка в том — что бы везде было одинаково.
Я предпочту получить ошибку или предупреждение при компиляции на несоответствие типов, нежели успешную компиляцию при которой, для примера, 0x80000000 перестанет быть отрицательным числом.
Я бы полностью согласился, если бы предлагали не long, а int64_t или, возможно что-нить из этого семейства.

Мои задачи все такие, где лиших 4 байт жалко. При чем по всем статьям. И по части памяти, и по части производительности, и по протокольной части. У меня почти везде код выглядит примерно так (применительно к обсуждаемой теме)
time_t local_time;
typedef struct t_msg {
uint8_t from:4;
uint8_t seq:4;
...
uint32_t remote_time;
...
uint16_t crc16;
} proto_mgs;
...
void do_recv_pkt(proto_msg *msg)
{
  time_t remote_time = (time_t)msg->remote_time;
  if(local_time < remote_time) {
    ...
  }
}


И примерно так я обращаюсь со всеми подобными типами. Увы, но их размерность в том же IAR ARM частенько зависит от настроек компилятора, а хочется загрузчик минимальный, в основном коде не изобретать велосипед, а еще и при необходимости собирать и то и дугое GCC. Потому там где это важно используются типы фиксированного размера, а чтоб успокоить -Wall -Wextra (а потенциально и причие средства проверки) в коде использую исключительно libc типы. Для меня это стоит некоторого увеличения стека, но это лучше чем иметь поломанный формат сообщения при смене настроек или переходе на другой компилятор.

А выше предложение с long выглядит странной альтернативой. Я очевидно не могу воткнуть его в пакет — это очевидный слом протокольного слоя. И могу, конечно, воткнуть его вместо time_t, но при переходе между компиляторами будет лишний шум при компиляции. Я предпочитаю писать так, чтоб предупреждения компилятора генерировались только от моих
#warning ToDo: this code to long and need review


Впрочем, тут ситуция действительно довольно занятная. Я привык к существованию time_t, size_t, pid_t и множества других. Привык к тому, что их испльзование позволяет избежать шума при компиляции подразные платформы, и в целом считаю это правильным. Другими словами, если в сигнатуре я виду типы [text]_t, то я считаю такие типы машинозависимыми, и требующими некоторой аккуратности при работе с ними. И к time_t отношение ровно такое. Наверное поэтому у меня и не возникло никаких проблем при смене размерности time_t с 32 на 64 разряда.
если бы предлагали не long, а int64_t

Когда появился time_t еще не было intN_t. И, возможно, было бы int32_t time(int32_t *). Но в контексте спора int64_t — машинно-независимый и имеет постоянную разрядность, в отличии от time_t.
А выше предложение с long выглядит странной альтернативой. Я очевидно не могу воткнуть его в пакет — это очевидный слом протокольного слоя.

В пакет типа rpm или в пакет типа datagram? Предложение-то было не сейчас использовать лонг, а в принципе не определять time_t. И тогда никаких варнингов не будет.
я считаю такие типы машинозависимыми

В этом у нас с вами и расхождение: я считаю их машинно-независимыми, как intN_t. За исключением least/fast, которые явно определены как машинно-зависимые.
Возможно, мое негодование насчет time_t вызвано тем, что size_t или pid_t, в целом, остаются внутри платформы. А unixtime стал машинно-независимым стандартом настолько, что даже БД поддерживают такой тип данных.

зы. А с точки зрения экономии 4 байт можно было определить как unsigned, кмк это не поломало бы почти ничего, но дало бы еще 68 лет.
Пакет в моем понимании это безусловно datagram. Мне казалось что пример кода это однозначно обозначает.

В остальном да, согласен. Просто изменение разрядности pid_t и size_t происходило на моей памяти и вызвано было бурным ростом ресурсов и производительности. Наверное, именно по этой причине для меня time_t стал просто следующим из серии. Я грыз гранит науки по этой части как раз на size_t. Да, в целом и сейчас еще живут в эксплуатации платформы на которых libc'овской size_t 16 (уже редко) или 32 (довольно часто) бита.

Однако, не могу не согласиться — time_t на долгое время стал стандартом. В далекие времена говорили, что сейчас он 32-разрядный, но к тому моменту когда его будет не хватать, вычислительные мощности позволят сделать его 64-разрядным. Увы, не все слышали тот факт, что рано или поздно он станет большим. Ну, и небезизвестная проблема Y2K, показала все преимущества UNIX TIME перед другими форматами хранения времени. Так что смена разрядности ожидаема и закономерна. Да и момент для ее выполнения выбран довольно удачно. Все же десктопно-серверно-мобильный парк уже 64 разрядный. А отстающим достаточно пересобрать (правильно написанный код) код из исходников.
Пакет в моем понимании это безусловно datagram.

Тогда вы поимеете проблемы, если другая сторона не под вашим контролем и еще не перешла на 64-битное время.

вычислительные мощности позволят сделать его 64-разрядным.

Это можно было сделать и по-другому и я тоже раньше думал что проблема 2038 для тех кто использовал time_t — не проблема. Перекомпилировал и все. Пока мне в голову не пришло, что нет возможности всех заставить в одно мгновение, или хотя бы в разумно короткий срок, перекомпилировать свой софт и конвертировать хранимые данные. А некоторые, по ряду причин, в принципе этого делать не будут. Потому что «снято с производства».

Все же десктопно-серверно-мобильный парк уже 64 разрядный. А отстающим достаточно пересобрать

Вы выбрасываете огромный пласт встраиваемых и прочих промышленных систем, которых рядом с вами м.б. больше, чем десктопов и мобил. В т.ч. и новомодный IoT, где не то что 64 бита где-то за горизонтом, так и 8-битные никуда уходить особо не планируют. Древний 8051 до сих пор in production.
И перекомпилировать нечего. Прошивка проприетарная, производителю нафиг не надо. Даже если производитель еще живой, а не закрылся/продался/и т.д.
У меня есть принт-сервер, который tcp/ip уже поддерживает, а classless еще нет. И если сетка не /24 — ну извините. И, естественно, никакой новой прошивки нет и не будет.
По части первой — в пакете время uint32_t. У меня любое. Более чем достаточно для того, чтобы не огрести проблем еще как минимум лет 20. А за это время согласовать изменение в протоколе на 64 бита, протестировать ответное оборудование на переход через ноль, и при необходимости его заменить.

Собственно это ответ на вторую часть. Да, работает ныне множество станков 1900 года выпуска. И часть электронники середины прошлого века. Местами даже ламповой. И исходники не везде сохранились, и перекомпилировать одномоментно не получится. Все так.

Но все не так страшно, как видится после прочтения. i8051 рулит мотором, и ему глубоко до лампочки до текущего времени в UTC или любом ином формате. Нет нужды ему синхронизироваться хоть с кем-то кроме себе подобных. А если и есть, то переход через ноль не великая беда для таких применений. Старая вычислительна техника просто умрет. Есть свой ресур не только у конденсаторов (их хотя бы заменить можно), но и у плат, и у транзисторов и у микросхем. А вот их заменить будет сложнее. Новомодный IoT уже знает о том, что time_t 64-разрядный. Даже если прошивка (частично) закрытая. Да и опять — самому IoT плевать на время. Оно важно слою, который выше. А там уже далеко не 8 бит.

А вот тянуть до последнего, как это сделала небезизвестная корпорация в 1999'ом — однозначно плохое решение. Правда, хайп вокруг него как раз и показал — все критичное к работе со временем успевает адаптироваться, а некритичное переживет переход через ноль и его никто не заметит. Только устраивать системам повторую проверку… Хм… Думаю, все же нецелесообразно.
По части первой — в пакете время uint32_t.

Это читтерство. Не time_t.

i8051 рулит мотором, и ему глубоко до лампочки до текущего времени в UTC или любом ином формате.


А еще он рулит фискальным регистратором, и ему ооочень не до лампочки насчет текущего времени. Как и проверяющему из налоговой. :)

Новомодный IoT уже знает о том, что time_t 64-разрядный.

Ну да, ну да… вот только что попробовал: arduino ide 1.8.5: 02 04 04, это sizeof int, long, time_t

Да и опять — самому IoT плевать на время.

Трекеру, камере, да банальному замку СКД — вряд ли плевать на время. IoT это же не только светодиодиками весело мигать, да свет в доме через облако со смартфона включать. А прожить встраеваемое решение может очень долго. Упомянутый мной принт-сервер года так 96-97.

А вот тянуть до последнего

Я же не предлагаю тянуть до последнего, а просто добавить новый тип. И функции дублировать не надо, достаточно для каждой иметь небольшой врапер, который на входе из 32 бит сделает 64, а потом на выходе обрежет обратно. В конце концов есть же lib64 рядом с lib, а programm files (x86) рядом с programm files. И ничего, никто вроде не сдох?
С чего читерство? В пакете как раз предельно справедливо. time_t в коде.

А вот с фискальным накопителем — крайне неудачный пример. Его срок эксплуатации максимум 3 года. Еще 5 лет он должен храниться. Итого 8 лет предел. Сколько поколений еще сменится до переполнения? Потом накопитель этот принеобходимости перешивается прямо в кассе. Ерунда все это.

Трекер, камера или замок в ответственном месте тоже будут заменены. Безопасники куда по меньшим причинам замену хотят. А вне ответственного места… Да посмотрите на тот же тыртубчик — регистратор с правильно установленным временем уже редкость. И никого это не смущает. И упомянутый вами принт-сервер. Разве что-то поменяется если его часы собьются? Они как-то повлияют на его функционирование?

lib + lib64 — костыль. Вы тут K&R цитировали, а сами в открытую противоречите идеологии UNIX. В Linux от него избавляются (и довольно успешно), в MacOs избавились волевым решением, а та, что с флагом… Скажем так — заложница своих предыдущих архитектурных недоработок. Про BSD & Solaris ничего не скажу. Просто не в курсе, но, думаю, что примерно так же как в Linux. Да и не за тем оно нужно было. Ваше решение вполне вебе накрывается wrapper'ом через LD_PRELOAD. Зачем частное решение мастабировать до глобальных размеров?
С чего читерство?

с того, что у вас наружу не торчит «машинно-независимый тип». А что у вас внутри программы — это ваше личное дело, поскольку оно под вашим полным контролем.

А вот с фискальным накопителем — крайне неудачный пример.

Крайне удачный, поскольку фискальный регистратор это не фискальный накопитель (т.е. фискальная память) и, грубо говоря, и есть касса, только в виде принтера, который можно подключить к любому ПК. В ФР стоит и сменный блок ФП и сменный блок ЭКЛЗ. И проработать ФР может до тех пор, пока реализация очередной законодательной поправки не потребует аппаратных изменений.

Трекер, камера или замок в ответственном месте тоже будут заменены.

Да, потому что новая версия софта не будет с ними работать. И вас вынудят их поменять.

И упомянутый вами принт-сервер. Разве что-то поменяется если его часы собьются?

В нем нет поддержки бесклассовых сетей, маска сети высчитывается автоматически на основе класса адреса. И для 192.168.x.x она всегда /24.
Это был не пример использования time_t, а пример отсутствия обновления прошивки под новые стандарты. Хотя RFC с CIDR приблизительно того же года, что и девайс.

Вы тут K&R цитировали, а сами в открытую противоречите идеологии UNIX.

А что, простите, не так?
И все же это не читерство. Это опыт разработки. Я никогда не запишу в формате пакета UnixTime как это сделано здесь. Там будет uint32_t. И только в описании поля будет написано, что это количество секунд и далее по тексту. И уж если совсем прижмет, и поле расширить не получится, то я просто поменяю в свежей редакции точку отчета. Да и по совести говоря судя по 5 байтной DATE_TIME здесь (сктати, где описание поле интересно знать) вопрос уже не стоит остро даже в этой теме. Судя по всему вернулись к старому доброму BDС представлению. Ладно, подумаешь очередной костыль на ровном месте.

Про «нас вынудят» я принципиально оставляю без комментария. Если все равно вынудят, то толку плакаться?

А про принципы UNIX. Да пожалуйста — один из них явно гласит: «Если можно не добавлять новую функциональность, не добавляйте её.» А вы предлагаете именно это. Да еще и с предельно четким дублированием функционала.

Впрочем, предлагаю завязывать. Я не горю желанием вас в чем-то переубеждать. Больше — частично признаю вашу правоту. Но, боюсь, позиция ваша в меньшинстве. Потому так или иначе, а придется под большинство подстраиваться. Сочувствую. И все что могу посоветовать — это четко проектировать свои решения всегда помня о том, что «котеночек подростает».
И все же это не читерство. Это опыт разработки.

Внутри вашей разработки вы можете крутить типами как угодно. Я говорил об обмене с внешним миром. И если вы не можете при этом использовать «машинно-независимый тип», то зачем он нужен?

А вы предлагаете именно это. Да еще и с предельно четким дублированием функционала.

Ну так можно договориться до того, что short/int/long — это дублирование функционала.

Но, боюсь, позиция ваша в меньшинстве.

Даже не сомневаюсь, что миллионы мух не могут ошибаться. :)
Речь не о большинстве/меньшинстве, и не о моем уходе в подполье, а о самом типе данных в целях использования для обмена информацией в гетерогенных системах.

четко проектировать свои решения

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

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

Вы решили, что раз тип «машинно-независимый», то значит его можно использовать для обмена информацией. Не лишенный здравого смысла вывод.

Только вот для людей «старой закалки» единственным реально «машинно-независимым» типом данных являются типы c явным указанием размерности. А time_t, size_t и им подобные возникли в том момент, когда языковых средств (char, short, int, long) оказалось недостаточно. Когда вдруг выснилось, что int во write() или lseek() должен отличаться от int'а в kill() и оба они должны не соответствовать int'у в mktime() или time() даже в пределах ОДНОЙ машины. Разве кто-то сказал, что даже 16 битные машины не должны работать с дисками и файлами больше 64Гб?

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

И совсем не надо изобретать новый стандарт. Достаточно знать «историю вопроса» и понимать основные принципы того, что заставляет стандарты переписываться, а новые типы данных появляться.

в наше время никому верить нельзя.

Мне можно (с) youtu.be/pgYKbfBkK8k
Только вот для людей «старой закалки» единственным реально «машинно-независимым» типом данных являются типы c явным указанием размерности.

А вот и не угадали. Типы с явным указанием размерности появились, емнип, только через 20 лет, в C99. Это относительно новая фича. А до этого предлагалось «make an appropriate set of choices of short, int, and long for each host machine».
Языковых средств хватало, *_t, повторю, не являются новыми типами, это синонимы к существующим. К тем самым char/short/int/long, для того, что бы получить нужный диапазон значений не зависимо от архитектуры. И во всех функциях (в пределах архитектуры) int будет одинаковый. Другое дело, что там не везде int, а тот тип, который дает нужный диапазон значений. Прикрутив поддержку 64Гб заменой типа вы тут же отломали поддержку 4Гб, и не сможете смонтировать старый диск, потому что где на нем было 16 бит, вы теперь ожидаете увидеть 32, а вместо 32 — 64.
Стандарт для того и нужен, что бы не было разночтений. А иначе это не стандарт.

зы. массовое решение не означает правильное решение.

POSIX — это не про двоичную совместимость и переносимость. Это про совместимость на уровне исходного кода. Первый стандарт 1988 год — мне было 9 лет и я еще в машинки и пистолетики играл. Первые идейи заняться программированием возникнут только года через три. И те мне менее, предназначение POSIX как раз и состояло в том, чтобу избавиться от этого самого «for each host machine».

Так что c «начистить морду» сильно опоздали. Раньше надо было.
А у вас есть текст posix 1003.1-1989? Ну или 1990? Даже не смотря на то, что это уже +15 лет к появлению языка.
Для вас, м.б., это глубокое прошлое, но я начинал с конструкций f(v) int v; {...}.
Хорошо.

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

И только с появлением stdint и Posix он стал тем самым «платформено-независимым ассемблером, с элементами структурного синтаксиса» (с)pfalcon Зачем возвращаться в пещеры и жарить мамотов?

Да, прямо отвечая на Ваш вопрос — первый детально изученный мною тест Posix датировался 2001 годом (IEEE Std 1003.1-2001). Правда было это в далеком 2002ом и схемотехника тогда мне была интереснее. Так что читался скорее не как стандарт, а как справочник. Но тем не менее.
Этот язык с самого начала был высокоуровневым ассемблером, достаточно сравнить с другими языками насколько легко и непринужденно он позволяет выстрелить себе в ногу, причем многократно.
Определение авторов: C is a relatively «low level» language. This characterization is not pejorative; it simply means that C deals with the same sort of objects that
most computers do, namely character, number, and addresses.
Стандартные типы для того и нужны, что бы не писать уникальный код под каждую систему и архитектуру там, где может быть реализован общий алгоритм. Я всегда отделяю форматно-, системно-, аппаратно-зависимые куски от основного кода, разве это не логично?
А вы, повторю, ссылаетесь на стандарт, который появился спустя 25 лет.
Хорошо, допустим я тупой. Допустим ничего не понимаю в написании низкоуровнего кросплатформенного кода. Допустим.

Но объясните же мне, наконец, почему
ssize_t write(int fd, const void *buf, size_t count);

хуже, чем
int write(int fd, const void *buf, unsigned int count);

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

Потом, лично для меня фраза
Я всегда отделяю форматно-, системно-, аппаратно-зависимые куски от основного кода...

несколько лукава. Где отделяем? В пределах файла, модуля, проекта, изделия? И если отделяем, то почему в переносимом коде не может быть time_t, а в протокольном uint32_t или unit64_t (в зависимости от того, кто с нами работает и насколько нам подвластно уговорить его изменит протокол обмена)?

Про стандарты. Хорошо, я ссылаюсь на стандарт, который появился через 25 лет. Но, черт возьми, он уже 20 лет как действует. В чем проблема-то? В том, что вы 20 лет с ним безрезультатно боретесь? Так желаю успехов в этой нелегкой борьбе.
Я не могу вам объяснить то, что вы сами себе придумали.
Давайте вы лучше мне расскажите: есть time_t, short, int, long. Какой надо выбрать для передачи информации о времени в другое устройство?
[u]intN_t, напомню, появится в далеком будущем, которое для вас не менее далекое прошлое.

зы. А мух от котлет я отделяю везде, где только можно.
Кратко я ответил в предыдущем сообщении. Развернуто готов повторить.

Итак, перед нами черный ящик, который должен обмениваться с подконтрольным нам устройством временем. Отлично. Давайте думать.

Первый (наиболее распространенный) случай. Черный ящик стоит давно, вполне себе успешно выплняет свои функции, но следов разработчика и вменяемой докуметации (даже пользовательской, не говоря уже о конструкторской) нет. Все что удалось найти — это частично документированный, частично отреверсированный протокол обмена. При этом переделывать черный ящик заказчик категорически отказывается (он же работает, вы просто телеметрию с него снимите и команды передавайте). В этом случае мы повязаны по рукам и ногам. Как сделано в черном ящике, так ему и отдаем. И точка на этом. Все, что мы можем (и должны) сделать, это предупредить заказчика и возможных проблемах со временем (если они, конечно, будут). Типа после 2038 года будет давать 1901 год. И мы с этим ничего сделать не можем. Впрочем, если очень захотим, то можем подставить костыль. Но костылей мы не любим, потому о такой возможности даже не упомянем. Опять же — как минимум до 2038 года. А там, глядишь, и переделка черного ящика окажется более выгодной. Как заказчику, так и нам.

Второй вариант — черный ящик вполне современный, и до его авторов можно достучаться, но они утверждают что «До 2038 года далеко, гарантия и срок службы к тому моменту закончатся — а значит не проблема. Во всяком случае не наша проблема.» Ну что — можно сказать ситуация аналогична предыдущей. С ровно таким же решением.

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

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

По поводу формата даты. Не знаю. Очень хороший вопрос. Пожалуй, я бы все же смотрел по месту. Крайне соблазнительно сказать, что сегодня это uint64_t (но обязательно обговорить endian). Но это, черт возьми, 8 байт. Возможно, uint8_t day, uint8_t month, uint16_t year, uint8_t hour, uint8_t minute, uint8_t sec для каких-то задач окажется предпочтительнее. Впрочем, вероятность ошибки (hour>23, day = 0 и т.п.) плюс потенциальные проблемы с локализацией (привет AM/PM) практически полностью ставят крест на таком формате. Правда, его семь байт можно подрезать. Скажем, для даты достаточно 5 бит вместо 8, для месяца 4, год до 65535 не обязательно. Можно ограничиться 4095 и скостить 4 бита. Это, конечно, чуть сложнее, но… Но задачи разные бывают. Опять же — временная зона. В случае uint64_t с ней надо что-то делать, а в случае BDC четко оговаривать — местное время или UTC, и что делать со смещением.

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

P.S.
То, что тип uint64_t в языке C повится в далеком то ли прошлом, то ли далеком будующем, не отменяет того факта, что 64-разрядное целое можно использовать в протоколе обмена между устройствами. Очевидно же. А уж какой конкретно тип изберет устройство — это его вопросы. Может и никакой. Может будет старшие 32 бита тупо игнорить считая их зарезервированными, а может по ним менять точку отсчёта эпохи UNIX. Варианты есть.
Ваши рассуждения хороши, когда число ящиков можно пересчитать по пальцам одной руки опытного фрезеровщика. А когда число и цвет ящиков неограниченны, нужен заранее определенный стандарт. Который если и меняется, то всегда можно было бы определить какая именно версия стандарта используется, а следовательно какие данные и какого размера будут использованы.
Абстрагируйтесь от сегодня. И даже от завтра. Попробуйте принять решение, находясь в позавчера. long long, по-моему, тоже чуть позже появилось.
Конечно, в общем случае можно использовать и 64-битные значения и даже 1024-битные, но речь не о размерности, а со сохранении размерности между системами.
Вы хотите, что бы дискета, записанная на одном устройстве прочиталась на другом?
BDC или uint64_t. При этом uint64_t все же предпочтительнее. Я готов понять и простить тех, кто в 80-90 прошлого века использовал uint32_t. Я не готов простить тех, кто в 2020 продолжает его использовать имея возможность выпустить свежую версию протокола обмена.

Вот мой абстактый ответ на ваш абстрактный вопрос. Остальное требует уточнения.

И да, число ящиков никогда не бывает неограниченным. Точно так же не бывает универсального протокола обмена между ними. Вон IPv4 когда-то должно было хватать всем и на все времена. Но увы. К счастью, прогресс не останавливается. И протоколы меняются.
Вы опять используете «здесь и сейчас», а не «там и тогда». И речь не целесообразности 64-битного времени, а о вариантах реализации этого.
Кстати, ваш пример с ip-адресами просто в десяточку: v4 оставили как есть, хотя по вашей логике, видимо, следовало бы добить его еще 4 октетами или расширить октеты с 8 бит до 16. Или сразу до 32. Или добавить еще 4 октета и все расширить до 16 бит.
Но тем не менее, вместо этого сделали новую адресацию.
IPv4 планомерно заменяется IPv6. Или обрастает костылями типа NATа. И если первое «в десяточку» в моем исполнении, то второе «в десяточу» от вас.

По объективным и абсолютно независящим ни от кого причинам IPv4 умрет (или впадет в анабиоз, как это сделало FIDO). Никого не удивляет смена msdods, windows 3.1, windows 95 и далее до windows 10. Никто не кричит оставьте dos как есть. Пока экономически целесообразно — будет legacy. Перестанет быть целесообразно — будет modern. Объективная реальность такова. Какой бы удобной не была RT-11 или CP/M их уже мало кто помнит.
Заменяются, но новые системы прекрасно уживаются со старыми стандартами. А не используют несовместимую со старой новую адресацию.
dos как есть, кстати, берите freedos и ностальгируйте. А с некоторыми оговорками приложения дос неплохо так работали под вин. Пока не появился far и wc/tc в окошке вполне мог работать nc/vc/dn и т.д. Пока под вин не появилось достаточно приложений многие вообще ее использовали как многозадачный дос.
И вы опять заявляете о необходимости изменений, что не вызывает возражений, вместо того, что бы говорить о вариантах реализации этих изменений. И ваши же примеры как раз показывают как сделать новое не сломав старое, а дав ему спокойно доработать свой срок.
ну пилять ребята, я не могу понять вас. Допустим есть архитектура с 32 битным интом. у вас есть код под эту архитектуру. Этот код перенесеться на 64 битную архитектуру, ок ладно… но как ты перенесешь тоже самое на 8 битную… точнее зачем, тот же вопрос про перенос на 64 битную… зделать 64 битную архитектуру что бы пернести код оптимизированый под 32 битную систему. Ну серьезно ребят… это не серьезно.
Почитайте про Posix. Хотя бы на WiKi.

Переносимость кода — вешь великая. Посмотрите на зоопарк архитектур, поддерживаемых Linux. Да что там Linux, посмотрите на FreeRTOS. С последне, правда, беда.
 xSemaphoreTake( SemaphoreHandle_t xSemaphore,  TickType_t xTicksToWait );

совсем не похоже на сигнатуры Posix'овские, но при желании к ним приводится.

А оптимизация… Нет, я не говорю что она не нужна. Просто оптимизировать имеет смысл архитектуро специфическую часть. Тогда не будет проблемы в черном ящике заменить контроллер на современный и перейти на 64-разрядный time_t в протоколе обмена с внешним миром. Иначе можно написать очень крутой код, но… Срок его жизни будет очень органичен. Тоже подход, конечно.
странные у вас рассуждения.
int32_t a,b,c;
c=a+b;
И вам не все ли равно как компилятор это обработает на разных платформах? Ну да, на 8-битных будет немного медленно. Но кмк сдвиги через С и сложение с использованием С как раз и предназначены для арифметики с разрядностью больше, чем умеет АЛУ в явном виде.
А для чего конкретно? Ну, например, crc-32 посчитать.
И ваши же примеры как раз показывают как сделать новое не сломав старое, а дав ему спокойно доработать свой срок.


Если вам показалось, что я выразился достаточно ясно, вы неверно меня поняли. © Алан Гринспен

Не припомню, чтоб я где-то призывал ломать старое, не дав ему доработать свой век. Я, как раз, призывал к обратному. Но есть тонкая грань между «дать доработаь свой век» и «превратить в икону и равняться для реализации». Еще раз перечитайте про черные ящики. Все там.

И да мосты 4to6 и 6to4 существуют. Но это мосты. И никакой прямой совместимости нет. Под Win работали Dos приложения. Но не все, и не всегда. И если до поры, совместимость с DOS приложениями была требованием, то сейчас на это забит большой болт — если надо есть виртуалки.

С time_t ровно так же. Ну был он 32 разрядным. Хорошо. Кто не может сделать его 64 разрядным будет получать его «как привык» через мостики и прослойки и доживать свой век. Вполне себе типичная ситуация. На кой черт понадобилость дублировать функциональность вводя time64_t я так и не понял.
Я не про мосты, а про то, что in_addr, так и остался 4 байта. И функция inet_aton так и работает с адресами v4.
А для v6 и структура in6_addr, и функция новая inet_pton, которая, впрочем, теперь реализована «форматно» независимая, хотя сейчас поддерживает только два формата: ipv4 и ipv6.
Этот бы комментарий, да на много этажей выше… Аплодирую стоя. Он ломает всю мою стройную конструкцию.

Нет, конечно, она устоит. Но не могу не отметить, что IPv4 стал стандартом дефакто, и еще долго будет напоминать о себе. «Если бы я был директором», то, пожалуй, сделел бы все, похожее на netinet/ip.h содержащими предупреждение о скором удалении и включающим netinet/ip4.h Уж не знаю сколько бы надо было это поддерживать, но оно бы того стоило. Возможно, что-то такое имеет смысл сделать и с time.h. Авторы IAR ARM, например, пошли именно таким путем. По умолчанию time_t 32 бита, но с преопределенным символом он становится 64-битным.

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

P.S.
И все же IPv4 и IPv6 разные протоколы. И IPv6 никогда не ставил целью заменить IPv4 в написанном коде.
Битовые поля применимы к регистрам, которые нельзя модифицировать одной записью в 32бита, например CONTROL_Type. Таких регистров очень мало, и применяются они крайне редко.
С другой стороны — битовые поля очень сильно увеличивают размер кода, потому что каждая запись в битовое поле — это безусловная модификация всего регистра.
Битовые поля применимы даже к переменным. Про размер кода — по разному. Обычно не сильно. Даже на малых уровнях оптимизации компилатор достаточно умен для того, чтоб сделать бе операции
BLOCK->REG |= (1<<POS);
BLOCK->REG.bit.POS = 1;

одинаковыми на уровне ассеблера. Больше того, на -Onone первая может еще и не оптимизировать сдвиг (редко, но встречается).

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

Битовые поля а.b.c.d…
Безусловная модификация — любое битовое поле можно менять в произвольном порядке, или все сразу.
Модификация с набором условий — это когда битовое поле a. нельзя менять после модификации (с записью) битового поля d. Но можно всё сразу одной записью, или нельзя…
Описание и применение подобных условий доступно в С++, хотя применяют его не часто, а сам код больше похож на магию. В более простом Си — таких возможностей не существует, по этому на всю битовую структуру вещается volatile, и любая модификация означает чтение/изменение/запись.
Насчёт умных компиляторов — слишком большое преувеличение.
Код в котором используются константы — может быть просчитан заранее. Но если есть хотя-бы одна живая переменная — то начинается пережёвывание стека.
Я не знаток плюсов. Но, полагаю, что если вокруг простых битовых полей построить магию с последовательной записью и запретами модификации код получится безумно страшным и похожим на магию. К счастью в C все сильно проще. В итоге и код проще. Что в листинге, что на машинном языке. Насколько я понимаю, нет никаких возможностей описать запрет одного из битовыхх полей, после (или без) модификации другого. Все же язык С пишут люди близкие к железу.

Что до volatile, то меня немного забавляет ситуация вокруг него. Сначала SUN посчитала, что для блока asm {} volatile необходим. Они, видети ли хотят оптимизировать тот ассемблерый блок, который я написал сам. И чтоб явно это запретить надо писать volatile asm {}. Потом с подачи Intel и безопасников стало модно ставить его куда попало. А всего-то надо понимать как именно работает твой инструмент. Для выкидывания обращения к памяти долны быть очень веские причины. По сути, разве что статусные регистры должны быть помечены этим спецификатором. Т.е. только те места, которые реально изменяются железом независимо от нашего кода. А его зачем-то используют везде по делу и без.

По поводу умных компиляторов и преувеличения. Я только сразу поправлюсь — речь идет исключительно о языке C (без всяких плюсов). Да, есть несколько этапов в развитии программиста, когда он с трудом находит общий язык с компилятором.

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

Второй этап бывает много позже. Когда жутко оптимизированный код на C не дает ожидаемого прироста производительности. Тогда с грустью обнаруживается, что -Omax (или ее аналоги) являются совсем не тем, что ожидалось. И их фишки в данном конкретном случае только мешаются, а максимально близкий к ожидемому результат получается только при -Onone. Вот тогда оглядываешься на свои код и понимаешь, что написан он так, что толку с -Omax сильно меньше, чем вреда. С другой стороны, -Onone уж слишком многословен по выходу. None это все же None и очень многое зачем-то вычисляется на этапе исполнения. Вот тогда случается следующий шаг к дзену, и обычный уровень оптимизации меняется с -Omax, на -O1 или -O2. Другими словами научились писать так, что код сохранит работоспособность при любом уровне оптимизации, но лучше всего будет работать при очень небольших.

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

Но, возвращаясь назад, до понимания назначения volatile и register надо дожить. Если их необдуманно ставить во все возможные места, то от первого страдает качество итогового кода, а второй становится просто бесполезен. Это все я к тому, что
Но если есть хотя-бы одна живая переменная — то начинается пережёвывание стека.

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

Язык Си самый простой… Я тоже так думал, но оказалось что ассемблер простой, а всё что выше него — уже намного сложнее.
gcc.gnu.org/onlinedocs/gcc Эта ветка, и ресурс sourceware.org целиком. Там очень легко заблудиться, и долго копать не в том направлении. Но именно эти документы меня часто выручали.
Sign up to leave a comment.

Articles