All streams
Search
Write a publication
Pull to refresh
37
0
Send message
Для конструкции A->B такого сведения нет. Вы, кажется, пользуетесь каким-то своим неформальным пониманием разыменования, не определив его явно.

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


Вы можете процитировать соответствующий пункт из списка undefined behaviours в конце стандарта?

Нет, навскидку не вижу там такого пункта. Но это не так важно, ибо соответствующий раздел — "informative", а не "normative".

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


The unary * operator denotes indirection. If the operand points to a function, the result is
a function designator; if it points to an object, the result is an lvalue designating the
object. If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an
invalid value has been assigned to the pointer, the behavior of the unary * operator is
undefined.102)

Сноска 102 однозначно поясняет:


Thus, &*E is equivalent to E (even if E is a null pointer), and &(E1[E2]) to ((E1)+(E2)). It is
always true that if E is a function designator or an lvalue that is a valid operand of the unary &
operator, *&E is a function designator or an lvalue equal to E. If *P is an lvalue and T is the name of
an object pointer type, *(T)P is an lvalue that has a type compatible with that to which T points.

Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer, an address inappropriately aligned for the type of object pointed to, and the address of an object after the
end of its lifetime.

Как вы умудрились не увидеть "аннигиляцию" мне не ясно, ибо в стандарте ясно сказано


If the operand is the result of a unary * operator, neither that operator nor the & operator is evaluated and the result is as if both were omitted

Это именно та самая аннигиляция. Опять же та же самая сноска 102 в первой своей части ясно говорит то же самое.


Где вы выкопали "семантика -> описана специальным образом" мне в упор не ясно. Даю под запись: семантика -> в С описана тем же образом, что и семантика *: указатель должен указывать на объект.


The value is that of the named member of the object to which the first expression points

Null-указатель не является указателем на объект.


примечание 69)

Что? Примечание 69 не имеет к этому никакого отношения. Что за "стандарт" вы там читаете?


С неформальной точки зрения, offsetof(A, B) это просто константа компиляции, и никакого разыменования, т.е. обращения к памяти, ее вычисление не требует.

В огороде бузина, а в Киеве дядька...


Никто не говорит, что offsetof(A, B) вдруг как-то требует обращения к памяти. Речь в данном разговоре идет о формальной легальности конструкции (size_t)&(((s *)0)-›m) в пространстве пользовательского кода. Сам offsetof тут вообще собоку припеку. И этк конструкция — нелегальна, ибо содержит применение оператора -> к указателю, не являющемуся указателем на объект.

Где‐то в блоге PVS‐Studio эта проблема обсуждалась: авторы данного статического анализатора считают, что, во‐первых, неопределённое поведение есть

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


Другое дело, что, как я уже сказал выше, этот критерий неприменим к коду, написанному на территории стандартной библиотеки, о чем было в свое время ясно сказано в DR#44 (http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_044.html).


Понятно, что в случае макроподстановок в общем случае трудно/невозможно отличить, откуда "пришел" код, по каковой причине такая реализация действительно будет отлавливаться анализаторами.


Реализация через глобальную переменную поможет решить обе проблемы оригинального offsetof: и устранит разыменование null-указателя, и устранит зависимость от физического значения null-указателя.

К чему вы это приплели? Что такое &(*((type *)NULL)) и к чему это здесь?


Напоминаю, что речь идет о выражении (size_t)&(((s *)0)-›m), которое, во-первых, содержит запрещенное разыменование нулевого указателя оператором -> и, во-вторых, завязано на физическое значение нулевого указателя на данной платформе.


Именно по этим двум причинам подобная реализация offsetof допустима только в рамках стандартной библиотеки конкретной платформы. Это не язык С.


Отдельно стоит заметить, что "создание адресной константы" обязано подчиняться всем остальным требованиям языка. Никакого отдельного разрешения на разыменование null-указателя в контексте "создания адресной константы" язык С не дает.


Однако средства статического анализа этого обычно "не знают" и начинают орать во все воронье горло на попытку разыменования null-указателя в offsetof. Именно поэтому в GCC приняли решение заменить реализацию offsetof не непрозрачный builtin.

Во-первых, повторяю еще раз: применять адресную арифметику к null-указателю в С запрещено. Где вы увидели в 6.5.3.2 хоть малейшее упоминание адресной арифметики — мне в упор не ясно. Адресная арифметика и ее правила в С определны в 6.5.6.


Во-вторых, разыменовывать null-указатель в С запрещено. Пункт 6.5.3.2, на который вы сослались, нигде не содрежит разрешения на разыменование null-указателя. Наоборот 6.5.3.2/1 это запрещает. Пункт 6.5.3.2 лишь говорит, что комбинация операторов & и * может "взаимно аннигилироваться" и тем самым предотвращать запрещенное разыменование.


В-третьих, "традиционная" хак-реализация offsetof(size_t)&(((s *)0)-›m) — построена на применении оператора -> к null-указателю. Оператор -> — это разыменование указателя.


В-четветых, работоспособность выражения (size_t)&(((s *)0)-›m) в качестве вычислителя смещения держится еще на том, что нулевой указатель соответствует физическому адресу 0. Такого язык С никогда не гарантировал. Физический адрес, соответствующий нулевому указателю — зависит от платформы. На некоей платформе этоможет быть 0xFFFFFFFF или 0xBAADF00D. На такой платформе такая реализация offsetof работать не будет.

Во-первых, применять адресную арифметику к null-указателям в С и С++ строжайше запрещено (undefined behavior). Поэтому о каком "совершенно законно" вы говорите и откуда вы это взяли — совершенно не ясно.


Во-вторых, "традиционная" реализация offsetof действительно делает разыменование null-указателя. Однако придираться к этому бесполезно. Как реализовано offsetof никого интересовать не должно. На реализацию стандартной библиотеки требования и правила языка С не распространяются. Спецификация языка гарантирует, что это стандартное макро работает — значит оно работает. Формально, стандартная библиотека не реализована на языке С вообще, а реализована на некоем платформенно-зависимом псевдокоде, любые сходства которого с языком С ограничены лишь интерфейсной совместимостью.

Это было сделано потому, что в компиляторах для embedded платформ эта фича была практически не востребованной, но в тоже время требовала заметных усилий в реализации. Именно для того, чтобы не тратить усилия реализацию ненужной фичи только ради того, чтобы компилятор стал conforming, VLA сделали опциональными.

Ну то есть просто тупо перевели статью из доков GCC…


Массивы run-time размера, надо заметить, являются расширением только в С++. В языке С это — стандартная фича начиная с С99.


Отдельно можно заметить, что "метки как значения" присутствовали на заре зарождения языка С — в CRM С.

Это лишь поверхностное сходство сходного синтаксиса.

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

А уж то, что большинство упоминаемых вами свойств (`std::vector`, `std::string`, `C-string`, `printf`, `std::iostream`, `malloc`) вообще реализованны *на уровне библиотеки*, делают их вообще неуместными в рамках данного вопроса. Никто вам не запрещает реализовать С-style строки или `malloc` в Паскале. Это, однако, не будет свидетельством какой-то «обратной совместимости» Паскаля и С.
Нет, неправда. Именно стандарт языка С гарантирует инициализуцию нулями всех статических объектов (неинициализированных явно пользователем).

> 6.7.8 Initialization
> 10 [...] If an object that has static storage duration is not initialized explicitly, then:
> — if it has pointer type, it is initialized to a null pointer;
> — if it has arithmetic type, it is initialized to (positive or unsigned) zero;
> — if it is an aggregate, every member is initialized (recursively) according to these rules;
> — if it is a union, the first named member is initialized (recursively) according to these rules.

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


Также:


Язык С

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

Это вранье. Все статические объекты в С инициализируются нулями по умолчанию.


Язык С++

Он имеет обратную совместимость с С.

Нет.


Однако имеются небольшие различия, из-за которых некоторые C-программы не удаётся скомпилировать компилятором C++.

Различия С и С++ — огромны и фундаментальны. С — lvalue-discarding language, C++ — lvalue-preserving language. Уже этого достаточно для того, чтобы заявлять, что любые сходства между этими языками — не глубже уровня сходного синтаксиса.


Вызов std::string::c_str() требуется для преобразования std::string в char*. Из самого мощного языка мы всегда имели бы полностью принимаемый перегруженный operator const char* () const.

Косноязычие не позволяет понять, что именно имеется в виду.


Разработчикам, возможно, придётся побеспокоиться о вопросах оптимизации, таких как, например, объявлять функцию inline (встраиваемой) или нет; но после принятия такого решения оно является только предложением: компилятор может решить, что предложение неправильное, и не принять его. В чём смысл? Должны ли разработчики беспокоиться о вопросах оптимизации?

Уже давно было объяснено, что inline больше не имеет отношения к оптимизации. Этот спецификатор влияет лишь на то, как определение функции взаимодействует с One Definition Rule, и не более ни на что. В С++17 появятся inline-переменные, где новая роль inline сияет во всей красе.

Не слушают? Да что вы говорите? Опять девичьи мечты?

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

И, я вижу, опять начались томные рассказы про то, что «вам нужно»… Я же, как я уже не раз говорил выше, веду речь именно и только о языке С, а не том, что мне или кому-то еще лично нужно.

Ну что ж — и наше вам с кисточкой.

Молодой человек, вы со своими "ссылками не первоисточники" смачно сели в лужу именно так, как садится в нее практически каждый новичок, неправильно понявший 6.3.2.3. Вы у меня уже, наверное, тысячный...


Учитесь не просто читать, а еще и понимать прочитанное. Я вам там все подробно разъяснил, под вашей "ссылкой не первоисточник".


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

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


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


Цитата, которую вы привели, объясняет, что будет происходить при вычислении выражения (int *) 123. Цитата, которую вы привели, никаким боком не говорит о том, что в int *p = 123; вдруг магическим образом произойдет неявная конверсия типа int в тип int *. Ничего подобного в вашей цитате не сказано.


А в начале раздела 6.3 ясно сказано


6.3 Conversions
1 Several operators convert operand values from one type to another automatically. This subclause specifies the result required from such an implicit conversion, as well as those that result from a cast operation (an explicit conversion). The list in 6.3.1.8 summarizes the conversions performed by most ordinary operators; it is supplemented as required by the discussion of each operator in 6.5.

Более того, в 6.5.4 Cast operators специально добавлено


3 Conversions that involve pointers, other than where permitted by the constraints of 6.5.16.1, shall be specified by means of an explicit cast.

А дальше вам прямая дорога в описание семантики инициализации, которая в свою очередь отошлет вас к описанию семантику оператора присваивания (а это тот самый 6.5.16 Assignment operators), где ни в коем случае не допускается комбинации из указателя и целого числа в присваивании, за исключением случая, когда справа стоит null pointer constant.


Собственно, статья права — никто не знает С.

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

О! Уж совсем детские наезды пошли!


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


Что касается вашей феерически "альтернативной" интерпретации работы этих компиляторов — здесь это никому не интересно. Здесь речь идет именно и только о языке С. И все эти компиляторы немедленно откажутся трактовать ваш ошибочный код, как код на С. В противном случае они бы под общий хохот были бы вышвырнуты с рынка смачным пинком под зад.


Что же касается вашей "практики" — это не практика, заблудший вы наш, это что-то вроде "диссертации Василия Ивановича", в которой он, согласно известному анекдоту, оторвав таракану ноги, пришел к выводу, что "безногий таракан не слышит". Или, если угодно, это — изучение творчества группы Битлз по напевам Рабиновича, согласно другому известному анекдоту.


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

Язык С — это язык, описываемый действующей на данный момент спецификацией языка. Язык С описывается спецификацией ISO/IEC 9899:2011, известной в популярной культуре, как С11.


Спецификация С89/90 описывает язык С89/90, а не язык С. Точно также как книга K&R С, описывает язык K&R С, а не язык С.


Создание указателя путем попытки неявного преобразования из int — это с точки зрения языка С не UB, а некомпилируемый код. При работе же с железом, когда это нужно, делается так: int *p = (int *) 123;, но ни в коем случае не int *p = 123;. Язык безусловно требует явного каста для выполнения такого преобразования.

Поведение там определено языком С, хотя и непереносимо (только для второго ворнинга).

Нет.


Программа некорректна — содержит constraint violations, т.е. синтаксические или семантические ошибки. Такая программа именно некомпилируема (!) как программа на языке С.


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


И для этого достаточно только main(), не говоря уже о int *p = 123;. Дальше main() уже можно не смотреть.


И не надо пытаться вертеть хвостом с вашим выходом за пределы массива. Он тут не имеет никакого значения. Кстати, выход за пределы массива не является constraint violation, т.е. компиляторы имеют полное право компилировать ваш выход за пределы даже и не пискнув. Диагностики времени компиляции тут не требуется.

Уважаемый, во-первых, то, что вам тут пытаются объяснить, это тот простой факт, что main() и int *p = 123; в языке С формально являются именно принципиально не компилируемым кодом. То, что ваш компилятор этот код проглатывает — это замечательно, но к языку С никакого отношения не имеет. И, соответственно, не имеет никакого отношения к теме этой дискуссии. С таким же успехом вы могли бы рассказывать нам, что там у вас компилируется в компиляторе Фортрана.


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


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


В-третьих, если я что вам и навязываю тут — так это сам язык С. Не больше, не меньше. За одиннадцать лет в JTC1/SC22/WG14 у меня выработалась такая привычка.


P.S. К чему здесь вдруг ваши разглагольствования на тему "что вы предложите мне выкинуть" — мне не ясно. Никто вам тут ничего выкидывать пока не предлагал.

С тем что формально требование стандарта соблюдено никто не спорит. В данной подветке речь идет не об этом, а о том о том, что делать выводы о корректности программы на основе того, как некий компилятор делит свои диагностические сообщения на errors и warnings — занятие совершенно бессмысленное. Тем не менее некоторые индивидуумы этого не понимают.


Это деление в случае GCC — не более чем "уютненькая" фантазия авторов компилятора, обилием каковых фантазий компилятор GCC широко знаменит. И подрастающее студенчество, формальных документов не читавшее, из-за этой "уютности" страдает заметно, как показывают в том числе и комментарии в этой ветке.


Когда-то нам стоило огромных усилий пробить разработку в направлении "педантичного" деления на ошибки и предупреждения в GCC (пресловутый -pedantic-errors), Почему это направление разработки вызвало такое сопротивление в GNU — мне до сих пор не ясно.

Неверно. И main() и int *p = 123; являются грубыми ошибками ("contraint violation") в одинаковой степени и в С, и в С++. Ваш компилятор С выдал требуемые диагностические сообщения, сообщив вам тем самым, что ваша программа не является программой на С и ее поведение не определено языком С. Точка.


Однако видя, что вы пока не умеете "на глаз" отличать "безобидные" диагностические сообщения от серьезных ошибок, я бы посоветовал вам почитать документацию своего компилятора и попросить у него помощи в этом. В частности, в GCC это делается флагом -pedantic-errors. Возьмите в привычку пользоваться этим флагом всегда


main.c:1:1: error: return type defaults to 'int' [-Wimplicit-int]
 main()
 ^~~~
main.c: In function 'main':
main.c:3:11: error: initialization makes pointer from integer without a cast [-Wint-conversion]
  int *p = 123;
           ^~~

Information

Rating
Does not participate
Registered
Activity