LINUX.ORG.RU

Поясните за Си

 , ,


0

3

Накопился ряд вопросов по Си, вываливаю сразу кучей.

1. Как посмотреть весь список типов, известных компилятору в данный момент с учётом включенных заголовочников, объявлений и т. д.?

2. Правильно ли я понимаю, что список собственно типов и структур хранятся отдельно, потому что Foo и struct Foo - разные типы данных и могут сосуществовать в рамках одного проекта?

3. В С11 появилось ключевое слово _Generic, однако ещё со стандарта С99 существует заголовочный файл tgmath.h, который реализует подобное поведение, аналогичное перегрузке функций в С++, для набора математических функций. Как это можно было реализовать в С99?

★★★★★

Ответ на: комментарий от anonymous

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

Раскусил, я до этого вcегда боялся использовать memcpy из-за UB. Наверное поэтому с работы выгнали.

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 2)
Ответ на: комментарий от kirk_johnson

When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object.

Как это правильно понимать? Что только при конверсии int* -> unsigned char* мы получим указатель на первый байт объекта int, а при конврсии int* -> void* -> unsigned char* мы получим первый байт объекта void, но не int?

Или при преобразовании int* -> void* результат это не указатель на void, а по-прежнему указатель на int и поэтому последующий каст к unsigned char* дас нам указатель на первый байт объекта int?

anonymous
()
Ответ на: комментарий от anonymous

Любой указатель может быть представлен как void *, а любой void * как T *. Также любой тип суть непрерывный набор байт, потому что типы в C существуют только в момент конпеляции.

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 2)
Ответ на: комментарий от kirk_johnson

Любой указатель может быть представлен как void *, а любой void * как T *.

Очевидно, неверно. Ты же сам копировал со стандарта пару дней назад, а уже не помнишь?

A pointer to an object or incomplete type may be converted to a pointer to a different object or incomplete type. If the resulting pointer is not correctly aligned for the pointed-to type, the behavior is undefined.

Так что если твой «любой void*» некорректно выровнен для T*, то UB.

Но вообще ладно, вопрос мой был не совсем в этом. Внимательно:

When a pointer to an object is converted to a pointer to a character type, the result points to the lowest addressed byte of the object.

Обрати внимание на артикли. Вот мне не до конца понятно, что из этого следует. Что для получения первого байта объекта T нужно обязательно кастовать T* -> unsigned char*, или можно T* -> void* -> unsigned char*?

Если последнее, то это означает, что T* -> void* (и вообще T* -> U*, при условии, что с выравниванием всё ОК) не делает указатель указателем на void (на U), а оставляет его указателем на T.

Тогда значит то, что мне говорили про C++17, является правдой. Что его правила каста указателей не новы (не любой каст указателя меняет значение указателя, где под «значением» понимается «смысл» указателя), а это просто явно сформулированные правила, которые действовали и до C++17. Эти же правила действуют и в C.

anonymous
()
Ответ на: комментарий от anonymous

Что для получения первого байта объекта T нужно обязательно кастовать T* -> unsigned char*, или можно T* -> void* -> unsigned char*?

В С++ только так и можно. Если сделать static_cast<unsigned char*> будешь послан. Придется использовать reinterpret_cast, который ни что иное, как static_cast<void*>
В С можно писать как угодно. Итог у этих операций один. Си работает с байтами, а не с сущностями

tyro33
()
Ответ на: комментарий от anonymous

Очевидно, неверно.

Очевидно, верно. Ты можешь скастовать T * в void * и обратно без потерь.

Обрати внимание на артикли. Вот мне не до конца понятно, что из этого следует. Что для получения первого байта объекта T нужно обязательно кастовать T* -> unsigned char*, или можно T* -> void* -> unsigned char*?

Может программирование — не твое?

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от tyro33

Придется использовать reinterpret_cast, который ни что иное, как static_cast<void*>

При этом про static_cast<void*> явно написано, что он не изменяет значения указателя. (и под «значением» понимается не битовое представление адреса).

Вот мне интересно, нет ли аналогичных, но подразумеваемых правил в C? Объяснения комитета под парой Defect Report-ов, которые я прочитал недавно, делают меня думать, что C тоже подразумевает что-то подобное. Т.к. в одном из ответов сказано, что побитового совпадения представления двух указателей недостаточно, чтобы у них было одно и то же значение (указатели одного и того же типа, естественно).

В С можно писать как угодно. Итог у этих операций один.

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

anonymous
()
Ответ на: комментарий от kirk_johnson

Очевидно, верно. Ты можешь скастовать T * в void * и обратно без потерь.

Ты написал, что любой void* можно скастовать в любой T*. А теперь оказывается что не любой, а только полученный из T*.

Может программирование — не твое?

А может — не твоё? Ты на простые вопросы ответить не можешь, хотя где-то там программировал на C.

anonymous
()
Ответ на: комментарий от kirk_johnson

Читать научись

Любой указатель может быть представлен как void *, а любой void * как T *

Ты можешь скастовать T * в void * и обратно

Утверждения с разным смыслом.

anonymous
()
Ответ на: комментарий от anonymous

Вот мне интересно, нет ли аналогичных, но подразумеваемых правил в C?

Есть. Называется strict aliasing rule. Актуальны и в С, и в С++.
В твоем примере с приведением к unsigned char* никаких нарушений нет. К указателю на символ можно можно корректно привести указатель любого типа. То же самое относится и к void*. Просто его нельзя кастовать в какой попало T* и использовать адресную арифметику.
Таким образом, касты T* -> unsigned char* и T* -> void* -> unsigned char* в сишке дадут одинаковый результат

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

Речь идет о касте именно в unsigned char*, а не T*. Но ты можешь привести в пример платформу, где не работает какой-нибудь memcpy, например. Оно как раз принимает void*, а оперирует unsigned char*, хе хе

tyro33
()
Ответ на: комментарий от tyro33

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

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от kirk_johnson

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

Что-нибудь с тегированной памятью. Интересно, как memcpy сделана на Эльбрусе.

tailgunner ★★★★★
()
Ответ на: комментарий от tyro33

Есть. Называется strict aliasing rule. Актуальны и в С, и в С++.

Опять 25... Откуда вы все лезете? strict aliasing rules это не правила приведения указателей. Хватит путать тёплое с мягким.

По правилам string aliasing можно получать доступ к объекту через lvalue символьного типа. Отсюда не следует, что указатель на объект можно кастовать к указателю на символьный тип. Для этого нужно отдельное правило. В C такое правило есть. А в C++ из указателя на объект получить указатель на байт(ы) его стораджа, видимо, нельзя. Но правило, разрешающее доступ через символьный тип, там есть.

Таким образом, касты T* -> unsigned char* и T* -> void* -> unsigned char* в сишке дадут одинаковый результат

«результат» в смысле «значение»? То есть, если указатель указывал на объект T и имел тип T*, то после каста к void* он по-прежнему указывает на T, и после последующего каста к unsigned char* мы получим указатель, который указывает на первый байт объекта T?

Но ты можешь привести в пример платформу, где не работает какой-нибудь memcpy,

Какая бы платформа ни была, в корректной реализации стандарта C на этой платформе memcpy не может «не работать» просто по определению.
Вопрос был в реализуемости memcpy на переносимом C. Способном работать на любой «платформе» с поведением, не противоречащим стандарту, каким бы извращённым оно ни было по сравнению с поведением на существующих платформах.

anonymous
()
Ответ на: комментарий от tailgunner

Что-нибудь с тегированной памятью.

Что-нибудь вроде реализации C в каком-нибудь прувере/формальном верификаторе.

anonymous
()
Ответ на: комментарий от tailgunner

в любом прувере будет модель memcpy

Сделай без неё.

формальный прувер - это не платформа.

А что такое платформа?

anonymous
()
Ответ на: комментарий от anonymous

в любом прувере будет модель memcpy

Сделай без неё.

Смысла нет. Впрочем, тебе уже показали реализацию memcpy на переносимом Си. То, что она тебя не устраивает - твои личные тараканы.

А что такое платформа?

ISA.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

То, что она тебя не устраивает - твои личные тараканы.

С чего ты взял, что она меня не устраивает?

anonymous
()
Ответ на: комментарий от tailgunner

А что такое платформа?

ISA.

В контексте реализуемости C платформа это любая реализация абстрактной машины C. Так что прувер вполне себе платформа.

anonymous
()
Ответ на: комментарий от anonymous

В контексте реализуемости C платформа это любая реализация абстрактной машины C

Ты имеешь полное право считать платформой то, что сам захочешь - прувер, ветвер, да хоть калах. А я буду считать платформой именно ISA.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Что-нибудь с тегированной памятью. Интересно, как memcpy сделана на Эльбрусе.

Гм... да точно так же, там glibc в «официальном» дистрибутиве. Можно ядерный глянуть, вдруг меняли. У меня вроде остались сырцы где-то :D

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от anonymous

Опять 25... Откуда вы все лезете? strict aliasing rules это не правила приведения указателей. Хватит путать тёплое с мягким.

Угу, strict aliasing про то, в каких случаях законно обращаться к объекту через lvalue другого типа. Действительно, причем же тут приведения?
Блин, тут даже компиляторы при кривых кастах ругаются на «dereferencing pointer does break strict-aliasing rules». Авторы, наверное, тоже путают теплое с мягким, лол :)

По правилам string aliasing можно получать доступ к объекту через lvalue символьного типа. Отсюда не следует, что указатель на объект можно кастовать к указателю на символьный тип.

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

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

В отличии от С, в С++ подобный доступ возможен только через явное приведение:

int buf[100];
char* n = buf; //С - ok, C++ - compilation failed

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

мы получим указатель, который указывает на первый байт объекта T?

В Си - да. Физически это будет первый байт объекта T. В С++ хз

tyro33
()
Ответ на: комментарий от kirk_johnson

Что-нибудь с тегированной памятью. Интересно, как memcpy сделана на Эльбрусе.

Гм... да точно так же

Точно так же с тегами не получится. Может, читать байт из середины float и разрешено, но чтобы было разрешено писать его в середину float - сомневаюсь. На lowrisc memcpy/memmove специальный.

там glibc в «официальном» дистрибутиве

Возможно, теговая защита памяти обычно отключена.

tailgunner ★★★★★
()

Поясните за Си

Переходите на Basic. Или Pascal.

anonymous
()
Ответ на: комментарий от tailgunner

Точно так же с тегами не получится.

Я наискосок посмотрел документацию по tagged memory — теги хранятся в отдельной зоне и доступ к ним через отдельные инструкции. Или я что-то упускаю?

Возможно, теговая защита памяти обычно отключена.

Заинтересовал. Попробую вечером потыкать.

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от kirk_johnson

Я наискосок посмотрел документацию по tagged memory — теги хранятся в отдельной зоне и доступ к ним через отдельные инструкции. Или я что-то упускаю?

Не знаю, где хранятся теги, но доступ к ним (и, вероятно, в обход них) наверняка делается специальными инструкциями, и в memcpy/memmove должны использоваться именно они. Правда, я не могу так сразу придумать, что будет, если, например, memcpy cкопирует половину float в другой float (можно придумать и другие приколы - пол-указателя, например).

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Не знаю, где хранятся теги, но доступ к ним (и, вероятно, в обход них) наверняка делается специальными инструкциями, и в memcpy/memmove должны использоваться именно они. Правда, я не могу так сразу придумать, что будет, если, например, memcpy cкопирует половину float в другой float (можно придумать и другие приколы - пол-указателя, например).

Судя по тому, что чуваки пишут «поддержка тегированной памяти пока что для тестовых целей» — ничего страшного не случится.

The implementation of tagged memory presented here provides only basic support by extending on-chip caches to hold tags and by adding a tag cache. Instruction set support is provided for testing in the form of load and store tag (ltag, stag) instructions. Future releases will add hardware support for particular security policies (e.g. generating an exception upon modifying data tagged as read-only) and Linux kernel support.

kirk_johnson ★☆
()
Последнее исправление: kirk_johnson (всего исправлений: 1)
Ответ на: комментарий от tyro33

Угу, strict aliasing про то, в каких случаях законно обращаться к объекту через lvalue другого типа. Действительно, причем же тут приведения?

Ни при чём.

Блин, тут даже компиляторы при кривых кастах ругаются на «dereferencing pointer does break strict-aliasing rules». Авторы, наверное, тоже путают теплое с мягким, лол :)

Это ты путаешь casting и dereferencing.

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

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

Какая-то странная логика. Точнее, её отсутствие.
«Объявление о продаже апартаментов за 100 млн. подразумевает, что у меня появились в кармане 100 млн., иначе зачем такое объявление?»

Как можно считать, что разрешение приведения подразумевается, когда явно (не подразумеваемо) написано, что каст к unsigned char* не даст указателя на unsigned char?

anonymous
()
Ответ на: комментарий от vodz

А что не так с va_list? Оно всегда было сделано через пропроцессорную магию, а в языке поддержка не va_list, а эллипса (...).

Стандарт называется «Programming languages — C». И в нём описывается va_list. Так что ... и va_list равноправны с точки зрения «поддержки в языке».

anonymous
()
Ответ на: комментарий от anonymous

Стандарт называется

Пф. Стандарт... Я начинал ещё со времен, когда v*-функции типа vprintf писать приходилось на ассемблере, не было никакого даже упоминания в стандарте, потом появился «стандарт» с varargs.h https://en.wikipedia.org/wiki/Stdarg.h#varargs.h и только потом stdarg.h

Так что ... и va_list равноправны

Ни откуда не следует. Ничего то вы не доказали.

vodz ★★★★★
()
16 февраля 2019 г.
Ответ на: комментарий от tailgunner

инкремент - это операция прибавления единицы...

И каким образом это разрешает любые арифметические операции?

Сдвиг беззнакового числа влево (E1 << E2) на число битов равное (или большее) числу битов в левом агрументе это UB, хотя 2 сдвига на половину от числа битов это не UB. Хотя вроде бы это «одно и то же» по своему конечному эффекту.

Так и разрешение инкрементов (прибавления единицы) не разрешает автоматически прибавления двух и т.д.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.