Использую упаковку структур на микроконтроллерах, когда необходимо эти структуры пересылать, например, между устройствами. Здесь очень важным становится длина посылки. Для этих целей использую typedef struct { ... } __attribute((packed)) mystruct_t; - даже при использовании указателей информация об упаковке данных не теряетя. Также часто важна непрерывность данных, тогда можно например uint8_t* кастить к своей структуре.
@@АлмазНазипов-ы4ф вводить в заблуждение можно каким бы то ни было утверждением. Единственное утверждение выше это то что указатель не инициализирован (не задано никакое значение, а значит по стандарту это "Undefined Behaviour". Вероятней всего там какие то мусорные значения, не ноль не NULL, а что угодно). И вполне может быть адрес на соседнюю структуру в памяти. И вот я вижу что у автора это работает и не понимаю по какой причине, для этого и задавал вопрос. А вопрос это не утверждение и стало быть фраза "не знаешь язык - других в заблуждение не вводи" вообще не к месту. Если чего то не знаешь то наилучший способ разрешить это это задать вопрос. Если ты велик и могуч и ко всему прочему понимаешь почему указатель без инициализации можно использовать, то неплохо было бы объяснить именно для этого и был задан вопрос :D Что думаешь Алмаз ?
Отличное видео, но как указал Максим Шишиморов проблема в том, что указатель не инициализиван и содержит мусор, что и вызывает Segmentation Fault, при верной инициализации все работает
Спасибо, про упаковку было полезно узнать) В высокоуровневых языках(java, python) теряется связь с пониманием как это всё интерпретируется в машинный код (будто смотрим на черный ящик), что не прививает привычку стараться понимать как это всё работает..
Все комменты по типу "Зачем? Мы в 2019!!! Ацтой". Объясняю один раз: то о чем говорится в видео должен знать человек, работающий с высоконагруженными системами, такими как написание ПО для спутника или программирование микроконтроллера где обьем памяти минимален. Для обычных программистов как вы это может быть бесполезно, но это не делает контент не актульным
Вот, если кому нужно - простенький js скрипт с функцией выравнивания байт и демонстрацией разницы между отсортированным и неотсортированными порядками байт: function GetAlignedStruct(structure) { var alignedStructure = []; var curAddr = 0; structure.forEach(element => { var remind = (curAddr += element) % element; if (remind) alignedStructure.push(element - remind); alignedStructure.push(element); }); return alignedStructure; } var struct = [1, 4, 2, 1]; console.log(GetAlignedStruct(struct)); struct.sort((a, b) => b - a); console.log(GetAlignedStruct(struct));
Не на всех платформах int32_t будет typedef для int. Так что это зависит от того, что вы хотите. Если вам важно получить 32 бита, используйте int32_t, а если вам важно только то, чтобы переменная была не меньше short и не больше long, и в будущих платформах может увеличиваться, то используйте int.
Я не могу представить сценария, когда программист использует переменную, и ему не важно какого она будет размера. int не является кроссплатформенным вариантом, т.к. неконтролируемое изменение свойств типа данных приводит к неизбежным багам и недочётам программы.
Не понятно зачем сейчас это всё нужно. При таких доступных объёмах ОЗУ, даже если ОС позволяет адресовать не более 2 ГБ, всё равно непрерывных данных будет гораздо больше, чем пропусков. Как пример, имеем два больших одинаковых по размеру массива. Описываем их как массив1 из int, затем что-то мелкое char, затем массив2 из int. Как будет это располагаться в памяти? Поправьте, но между массивами не будет дырки размером с массив2. Дырка будет размером с Int, что гораздо меньше всей структуры. Могу себе представить такую возню при программировании микроконтроллеров, но на ПК и даже на ARM это выглядит как экономия на спичках.
Вроде как компиляторы могут оптимизировать и чтение упакованных данных, так что и чтений будет меньше, а они происходят гораздо дольше тактов процессора.
Не верится, что сейчас, в 2019 все так ужасно. Программист вообще не должен думать о подобном. Неужели даже последние версии gcc не умеют автоматически упаковывать данные и заботиться о том, чтобы в коде ничего из-за этого не сломалось?
Я в видео даже обсуждал почему компилятор не может просто поменять местами переменные за вас. Это связано с тем, как переменные конструируются. Кроме этого подумайте, что произойдёт, если у вас ваша структура внутри union, при изменении порядка переменных вы можете сломать то, зачем вы делали union.
@@DabaevSS, программисты, для которых возня с компиляторами, архитектурами - их работа, а не для тех, кто хочет, чтобы его либа просто работала эффективнее. Это как решить проблему один раз, а не возиться с ней на каждом проекте каждому программисту
@@VladimirMozhenkov, подобные способ обращения с памятью - совсем другой разговор. В таком случае, программист оборачивает против себя весь тот ужас, с которым приходится работать разработчикам компиляторов. Хорошо, когда подобная грань между хорошим (безопасным) обращением с памятью и плохим (небезопасным) проведена на уровне языка, как это сделано в Rust. Ну этим я, собственно, и ответил на свой вопрос - семантика языка не позволяет подобные вольности компилятору
@Станислав Вешняков, возможно, что в таком случае операции прямого обращения к памяти, такие как *(int*)(ptr + 1), помечены как операции, вызывающие UB
Rah 837 вот как раз программист либы и может этим заняться, потому что для него это частный случай, он знает какие переменные используются, в какую память выделяются и знает где можно для скорости упаковать, а где данные уедут в сеть на другую машину с другой архитектурой. Требовать того же от разработчиков компилятора странно, у них нет этой информации.
Думаю все-таки сыграло роль то что вы не знаете С. Тут есть одна фатальная ошибка из-за которой приложение "падает", и это совсем не выравнивание памяти. struct Test* tp; // здесь вы не инициализировали переменную int* pp = &tp->i; // здесь вы обращаетесь к неинит. переменной, тут тоже может падать int* pp_correct __attribute__((aligned(1))) = &tp->i; // здесь этот аттрибут никак не поможет, компилятор и сам знает как нужно выравнивать printf("pp is %d ", *pp); // тут вы разименовываете указатель на память которую не выделили printf("pp_correct is %d ", *pp_correct); // тут тоже самое чтобы решить эту проблему нужно заменить строчку "struct Test* tp;" на: struct Test* tp = malloc(sizeof(struct Test)); memset(tp, sizeof(struct Test), 0);
Да ладно, указатель указывает на валидную память на стеке (struct Test t) так что утверждение о невыделенной памяти некорректно. Более того, в коде он даже ее инициализировал (t.i = 5), так что тут нет UB с точки зрения чтения неинициализированной памяти. Отсюда следует, что это как раз UB связанное с выравниванием
@@astmix1337 посмотри ниже, сверху он создал struct Test t; Но ниже создал struct Test* tp; И не инициализировал ее и использовал и t и tp, t - на стеке tp - указатель, но не инициализирован Там где работа с "t" - все ок, Все плохо там где работа с "tp" именно изза того что память для нее не выделенная
@@serhii-dzhus да, согласен) с первого раза не сразу заметил) так что извиняюсь), показалось, что там была строка tp = &t, но похожие названия других переменных сбили с толку)
Ааа, они там отменили кошельки с тамими номерами. Увы много видео уже отредактировано и я не буду только ради этого переделывать. Но всё в конечном счёте исправлю.
@@victorzedwings думаю в таком случае компиляторы бы всегда так оптимизировали, но доступ к не выровненным данным более медленный, конечно это все зависит от конкретного алгоритма
@@serhii-dzhus ,чем это он более медленный? Если куски по 14 байт, то да, возможно. А если вся структура умещается в 128 байт а память 2х канальная то наоборот быстрее. Сильно зависит от контроллера памяти. Например nVidia выполняет спекулятивную загрузку. Да и серверные контроллеры тоже знают как правильно читать...
@@victorzedwings процессорам "удобнее" читать данные выровненные по 4 или 8 байт, а если они не выровненные то процессор делает дополнительные манипуляции чтобы считать данные по невыровненому адресу
спасибо Володя!!! смотрел ролик как завороженный))
Спасибо Володя
Спасибо большое за видео. Готовлюсь к собеседованию, эта тема как раз является одной из ключевых, теперь чувствую себя уверенее!)
Использую упаковку структур на микроконтроллерах, когда необходимо эти структуры пересылать, например, между устройствами. Здесь очень важным становится длина посылки. Для этих целей использую typedef struct { ... } __attribute((packed)) mystruct_t; - даже при использовании указателей информация об упаковке данных не теряетя.
Также часто важна непрерывность данных, тогда можно например uint8_t* кастить к своей структуре.
Отличный урок, премного благодарен.
Что то не понятно как через не инициализированный указатель "tp" получается доступ к "t" ?
"не знаешь язык - других в заблуждение не вводи", зачем Вам это, автор?
@@АлмазНазипов-ы4ф вводить в заблуждение можно каким бы то ни было утверждением. Единственное утверждение выше это то что указатель не инициализирован (не задано никакое значение, а значит по стандарту это "Undefined Behaviour". Вероятней всего там какие то мусорные значения, не ноль не NULL, а что угодно). И вполне может быть адрес на соседнюю структуру в памяти. И вот я вижу что у автора это работает и не понимаю по какой причине, для этого и задавал вопрос. А вопрос это не утверждение и стало быть фраза "не знаешь язык - других в заблуждение не вводи" вообще не к месту. Если чего то не знаешь то наилучший способ разрешить это это задать вопрос. Если ты велик и могуч и ко всему прочему понимаешь почему указатель без инициализации можно использовать, то неплохо было бы объяснить именно для этого и был задан вопрос :D Что думаешь Алмаз ?
Спасибо!
Спасибо, просто, понятно и полезно
Отличное видео, но как указал Максим Шишиморов проблема в том, что указатель не инициализиван и содержит мусор, что и вызывает Segmentation Fault, при верной инициализации все работает
Ого, спасибо, очень наглядно.
Спасибо, про упаковку было полезно узнать) В высокоуровневых языках(java, python) теряется связь с пониманием как это всё интерпретируется в машинный код (будто смотрим на черный ящик), что не прививает привычку стараться понимать как это всё работает..
Все комменты по типу "Зачем? Мы в 2019!!! Ацтой". Объясняю один раз: то о чем говорится в видео должен знать человек, работающий с высоконагруженными системами, такими как написание ПО для спутника или программирование микроконтроллера где обьем памяти минимален. Для обычных программистов как вы это может быть бесполезно, но это не делает контент не актульным
Ещё это немного позволяет вникнуть в суть того, как именно работает компилятор и (что тоже немало важно) сам процессор.
Real Time OS
у спутника миним обьем памяти ? дану нах уй , ты че несешь🤦
Вот, если кому нужно - простенький js скрипт с функцией выравнивания байт и демонстрацией разницы между отсортированным и неотсортированными порядками байт:
function GetAlignedStruct(structure)
{
var alignedStructure = [];
var curAddr = 0;
structure.forEach(element => {
var remind = (curAddr += element) % element;
if (remind)
alignedStructure.push(element - remind);
alignedStructure.push(element);
});
return alignedStructure;
}
var struct = [1, 4, 2, 1];
console.log(GetAlignedStruct(struct));
struct.sort((a, b) => b - a);
console.log(GetAlignedStruct(struct));
Очень приятный преподаватель
Володя, откуда знаешь про процессоры arc? :)
Есть хоть одна причина использовать int вместо int32_t?
Не на всех платформах int32_t будет typedef для int. Так что это зависит от того, что вы хотите. Если вам важно получить 32 бита, используйте int32_t, а если вам важно только то, чтобы переменная была не меньше short и не больше long, и в будущих платформах может увеличиваться, то используйте int.
Если у вас 16-битный процессор, то int32_t был-бы бесполезным.
Я не могу представить сценария, когда программист использует переменную, и ему не важно какого она будет размера. int не является кроссплатформенным вариантом, т.к. неконтролируемое изменение свойств типа данных приводит к неизбежным багам и недочётам программы.
Volodya Mozhenkov это может быть обертка над 2мя 16разрядными числами с целью переносимости для случаев когда она важнее производительности
Не понятно зачем сейчас это всё нужно. При таких доступных объёмах ОЗУ, даже если ОС позволяет адресовать не более 2 ГБ, всё равно непрерывных данных будет гораздо больше, чем пропусков. Как пример, имеем два больших одинаковых по размеру массива. Описываем их как массив1 из int, затем что-то мелкое char, затем массив2 из int. Как будет это располагаться в памяти? Поправьте, но между массивами не будет дырки размером с массив2. Дырка будет размером с Int, что гораздо меньше всей структуры.
Могу себе представить такую возню при программировании микроконтроллеров, но на ПК и даже на ARM это выглядит как экономия на спичках.
Выравние данных определенным образом нужно, чтобы уложиться в кэш-линию, что может значительно увеличить скорость выполнения кода.
@@iamstillanon3048 спасибо.
Вроде как компиляторы могут оптимизировать и чтение упакованных данных, так что и чтений будет меньше, а они происходят гораздо дольше тактов процессора.
Не верится, что сейчас, в 2019 все так ужасно. Программист вообще не должен думать о подобном. Неужели даже последние версии gcc не умеют автоматически упаковывать данные и заботиться о том, чтобы в коде ничего из-за этого не сломалось?
Я в видео даже обсуждал почему компилятор не может просто поменять местами переменные за вас. Это связано с тем, как переменные конструируются. Кроме этого подумайте, что произойдёт, если у вас ваша структура внутри union, при изменении порядка переменных вы можете сломать то, зачем вы делали union.
@@DabaevSS, программисты, для которых возня с компиляторами, архитектурами - их работа, а не для тех, кто хочет, чтобы его либа просто работала эффективнее. Это как решить проблему один раз, а не возиться с ней на каждом проекте каждому программисту
@@VladimirMozhenkov, подобные способ обращения с памятью - совсем другой разговор. В таком случае, программист оборачивает против себя весь тот ужас, с которым приходится работать разработчикам компиляторов. Хорошо, когда подобная грань между хорошим (безопасным) обращением с памятью и плохим (небезопасным) проведена на уровне языка, как это сделано в Rust. Ну этим я, собственно, и ответил на свой вопрос - семантика языка не позволяет подобные вольности компилятору
@Станислав Вешняков, возможно, что в таком случае операции прямого обращения к памяти, такие как *(int*)(ptr + 1), помечены как операции, вызывающие UB
Rah 837 вот как раз программист либы и может этим заняться, потому что для него это частный случай, он знает какие переменные используются, в какую память выделяются и знает где можно для скорости упаковать, а где данные уедут в сеть на другую машину с другой архитектурой. Требовать того же от разработчиков компилятора странно, у них нет этой информации.
Думаю все-таки сыграло роль то что вы не знаете С.
Тут есть одна фатальная ошибка из-за которой приложение "падает", и это совсем не выравнивание памяти.
struct Test* tp; // здесь вы не инициализировали переменную
int* pp = &tp->i; // здесь вы обращаетесь к неинит. переменной, тут тоже может падать
int* pp_correct __attribute__((aligned(1))) = &tp->i; // здесь этот аттрибут никак не поможет, компилятор и сам знает как нужно выравнивать
printf("pp is %d
", *pp); // тут вы разименовываете указатель на память которую не выделили
printf("pp_correct is %d
", *pp_correct); // тут тоже самое
чтобы решить эту проблему нужно заменить строчку "struct Test* tp;" на:
struct Test* tp = malloc(sizeof(struct Test));
memset(tp, sizeof(struct Test), 0);
Да ладно, указатель указывает на валидную память на стеке (struct Test t) так что утверждение о невыделенной памяти некорректно. Более того, в коде он даже ее инициализировал (t.i = 5), так что тут нет UB с точки зрения чтения неинициализированной памяти. Отсюда следует, что это как раз UB связанное с выравниванием
@@astmix1337 посмотри ниже, сверху он создал struct Test t;
Но ниже создал struct Test* tp;
И не инициализировал ее и использовал и t и tp,
t - на стеке
tp - указатель, но не инициализирован
Там где работа с "t" - все ок,
Все плохо там где работа с "tp" именно изза того что память для нее не выделенная
@@serhii-dzhus да, согласен) с первого раза не сразу заметил) так что извиняюсь), показалось, что там была строка tp = &t, но похожие названия других переменных сбили с толку)
Что-то донэйт по вебмани не проходит: "Пользователь с таким идентификатором не найден.
"
Ааа, они там отменили кошельки с тамими номерами. Увы много видео уже отредактировано и я не буду только ради этого переделывать. Но всё в конечном счёте исправлю.
@@VladimirMozhenkov, редактировать все видео не надо, просто обновите кошельки в описании к каналу.
Кое-что-таки узнал.
*последлнее видео было просто какаойто стёб над писателями книг.*
*40 минут текста ни о чём. но строго научно.*
посмотрим каким это будет.
просто всегда надо использовать максимальный пакинг.
практика показывает что это всегда работает в разы быстрее.
ну это всегда экономит память, но ваш процессор от этого совсем не в восторге)
@@serhii-dzhus , очень даже в восторге. меньше циклов для загрузки в кэш.
экономней расход памяти и кэша соответственно
@@victorzedwings думаю в таком случае компиляторы бы всегда так оптимизировали, но доступ к не выровненным данным более медленный, конечно это все зависит от конкретного алгоритма
@@serhii-dzhus ,чем это он более медленный?
Если куски по 14 байт, то да, возможно.
А если вся структура умещается в 128 байт а память 2х канальная то наоборот быстрее.
Сильно зависит от контроллера памяти.
Например nVidia выполняет спекулятивную загрузку.
Да и серверные контроллеры тоже знают как правильно читать...
@@victorzedwings процессорам "удобнее" читать данные выровненные по 4 или 8 байт, а если они не выровненные то процессор делает дополнительные манипуляции чтобы считать данные по невыровненому адресу
2019 год - а компиляторы дерьмо, языки программирования - дерьмо.