LINUX.ORG.RU

[C] void* и переменное число аргументов

 


0

1

В ##c на irc.freenode.net встретился с мнением, что результат работы такой конструкции

int a; printf("%p", &a);
не определён, а правильно писать
int a; printf("%p", (void *)&a);
Я ещё могу представить себе, что это верно с точки зрения стандарта, но абсолютно не представляю, как может получится что-то кроме верного значения. Может ли это случиться в реальной жизни?

★★★★

Указатели все равны, значит первый случай тоже верный. Писать (void*) ненужное усложнение кода.

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

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

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

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

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

Не говори того чего не знаешь. При приведении классов по дереву наследования адрес указателя может поменяться (в случае множественного наследования). Гугли короче про то как устроены классы с c++/\.

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

>в случае с функциями с переменным числом аргументов никакого

преобразования нигде не происходит


Какое на хрен преобразование? Указатели всех типов имеют одинаковый размер.

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

> Какое на хрен преобразование? Указатели всех типов имеют одинаковый размер.

На x86 - да. Но вообще - нет, и стандарт требует, что бы этот момент учитывался при написании кода.

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

> и не припомнит упоминание про

Его там быть, в принципе, и не должно. Стандарт лишь описывает, как происходят преобразования указателей, а выводы делать должен сам программист :) Что касается ситуации с printf, то те, кто советуют явно преобразовывать указатель к void*, по видимому правы.

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

И всё же, есть ли пример, когда допустип без преобразования к void* на архитектуре x86 работает корректно, а на архитектуре x86_64 нет? Тогда такое преобразование будет нужным, просто конечно на первый взгляд преобразовывать не сложно, но всё же это усложняет код*)

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

>И всё же, есть ли пример, когда допустип без преобразования к void* на архитектуре x86 работает корректно, а на архитектуре x86_64 нет?

У вас x86 головного мозга

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

ты не догадываешь, что x86 и x86_64 это практически одна архитектура? а при этом существует туева хуча других

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

Не имеет значения с какой архитурой сравнивать, главное что-бы размер указателя был разный(Как раз как у x86 и amd64)Как я понял, действие с преобразование указателя к *void, как раз и направленно что-бы обеспечить совместимость с архитектурами, у которых разный размер указателя.

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

(void*) всегда один

требуется, чтоб void* однозначно описывал любой указатель

Это в С. К С++ это относится только к POD типам

namezys ★★★★
()

При вызове функций с переменным числом аргументов все указатели приводятся к void*, все целочисленные переменные меньше int к int, остальное не помню, но в общем в стандарте на этот счёт всё чётко расписано. Поэтому мнение неправильное, приведённые куски кода идентичны для компилятора, соблюдающего стандарты.

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

Хм. Я в стандарте такого не вижу

The parameter type shall be a type name specified such that the type of a pointer to an object that has the specified type can be obtained simply by postfixing a * to type. If there is no actual next argument, or if type is not compatible with the type of the actual next argument (as promoted according to the default argument promotions), the behavior is undefined, except for the following cases:
— one type is a signed integer type, the other type is the corresponding unsigned integer type, and the value is representable in both types;
— one type is pointer to void and the other is a pointer to a character type.

If the expression that denotes the called function has a type that does not include a prototype, the integer promotions are performed on each argument, and arguments that have type float are promoted to double. These are called the default argument promotions.

По стандарту явно приводить тип всё-таки нужно, похоже.

Gvidon ★★★★
() автор топика

AFAIK в Си90 и Си99 разрешили неявные преобразования из/в void*. Для более ранних версий стандарта преобразование надо делать явно и компиляторы должны кидать warning для первого случая.

ntp
()
Ответ на: Это, да? от m4n71k0r

Вопрос не конкретно о printf, а о функциях с переменным числом аргументов в целом.

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

В том-то и дело, что в данном случае преобразования не происходит.

Gvidon ★★★★
() автор топика

Может быть заморочка с множественным наследованием. А где еще приведение к (void *) важно?

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

>>AFAIK в Си90 и Си99 разрешили неявные преобразования из/в void*. Для более ранних версий стандарта преобразование надо делать явно и компиляторы должны кидать warning для первого случая.

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

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

>>AFAIK в Си90 и Си99 разрешили неявные преобразования из/в void*. Для более ранних версий стандарта преобразование надо делать явно и компиляторы должны кидать warning для первого случая.

Именно

Можно цитату из стандарта ?

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

>>Можно цитату из стандарта ?

Я спросоня не увидел, что в строке формата printf стоит «%p». Поэтому правильный ответ: «Да, второй фрагмент кода корректен, первый - нет». Объясняю почему. Согласно K&R, в 5.11 сказано, что переводить из любого указателя в void * и обратно можно без потери информации. И это создает обманчивое впечатление, что первый фрагмент кода верен. Но, к сожалению, автоматическое приведение указателей не работает с аргументами переменной длины. На этапе компиляции компилятор ничего не знает о типах аргументов, описанных в строке формата функции printf и не может выполнить автоматическое приведение указателей к void *, поэтому, если строка формата требует тип void * - то и нужно его привести к void *. То, что компилятор компилит и не выдает предупреждений не означает, что код правилен с точки зрения стандарта. В GCC, например, sizeof void * равен 1, хотя по стандарту это значение не определено и тот код с адресной арифметикой, который компилится GCC, может не собраться другим компилятором (MSVC, например).

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

Как я написал ещё в первом сообщении, понятно, что с точки зрения стандарта надо приводить тип явно. С другой стороны, я не представляю себе архитектуру/компилятор, на которой первый фрагмент приведёт к ошибке. Такое бывает?

Gvidon ★★★★
() автор топика

а правильно писать

int a; printf("%p", (void *)&a);

если уж сходить с ума, так до конца:

int a; printf("%p", (const void *)&a);

зы: я просто оставлю это здесь (п. 1 и 7):

ISO/IEC 9899:1999

6.3.2.3 Pointers

  1. A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.
  2. For any qualifier q, a pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.
  3. An integer constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant.⁵⁵⁾ If a null pointer constant is converted to a pointer type, the resulting pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function.
  4. Conversion of a null pointer to another pointer type yields a null pointer of that type. Any two null pointers shall compare equal.
  5. An integer may be converted to any pointer type. Except as previously specified, the result is implementation-defined, might not be correctly aligned, might not point to an entity of the referenced type, and might be a trap representation.⁵⁶⁾
  6. Any pointer type may be converted to an integer type. Except as previously specified, the result is implementation-defined. If the result cannot be represented in the integer type, the behavior is undefined. The result need not be in the range of values of any integer type.
  7. 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. Otherwise, when converted back again, the result shall compare equal to the original pointer. 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. Successive increments of the result, up to the size of the object, yield pointers to the remaining bytes of the object.
  8. A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer. If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.

___________________

(55) The macro NULL is defined in <stddef.h> (and other headers) as a null pointer constant; see 7.17.

(56) The mapping functions for converting a pointer to an integer or an integer to a pointer are intended to be consistent with the addressing structure of the execution environment.

(57) In general, the concept ‘‘correctly aligned’’ is transitive: if a pointer to type A is correctly aligned for a pointer to type B, which in turn is correctly aligned for a pointer to type C, then a pointer to type A is correctly aligned for a pointer to type C.

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

> При вызове функций с переменным числом аргументов все указатели приводятся к void*

Можно цитату из стандарта, а то мы всё сомневаемся ?

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

> зы: я просто оставлю это здесь
Несомненно, очень интересная информация, только какое отношение она имеет к теме? Ещё раз: printf ожидает void*, мы вместо этого подаём int*. В твоей цитате сказано, что любой указатель [b]может[/b] быть приведён к указателю на void, в случае функций с переменным числом аргументов приведение произведено не будет (да и не может быть). Чисто по стандарту явно приводить тип к void* нужно, это есть факт. Вопрос: нужно ли это делать и в реальности, т.е. существует ли реальный шанс получить проблемы, если это не сделать?

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

при передаче аргументов функции указатели должны приводиться к void* (без разницы, имеет функция интерфейс или нет), иначе медным тазом накроется мегафича «неполные типы» (incomplete types), которые эквивалентны void*. как-то так.

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

в смысле неявно приводиться, компилятором. от пользователя^Wпрограммиста этого стандартом не требуется.

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

<<Так при чём тут sizeof(void) и арифметика? Речь идёт о приведении указателей

Это я привел чисто для примера, когда одно работает с одним компилятором, а с другим - нет.

MuZHiK-2 ★★★★
()
Ответ на: комментарий от arsi

> при передаче аргументов функции указатели должны приводиться к void* (без разницы, имеет функция интерфейс или нет), иначе медным тазом накроется мегафича «неполные типы» (incomplete types), которые эквивалентны void*. как-то так.

Трудящиеся просят процитировать стандарт.

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

Мои 5 копеек. IMHO второй вариант «более по стандарту» (6.2.5 параграф 27), то есть возможна ситуация что представление void * отличается от int * и тогда в первом случае printf() будет интерпретировать совершенно другое.

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

да нет.. к не-pod типам возможно тоже... там человек выше случай с множ. наследованием написал, тогда да - ломается.. а так-то - с чего бы не работать? это же указатель на область памяти, разница pod и не pod-типов в том как они хранятся уже в памяти (но разницы в адресе начала блока памяти где хранятся данные нет же). разве что с выравниванием какие-нибудь траблы могут быть, я хз - в плюсах void* это однозначно кривой стиль программирования (как по мне).

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