2015-12-21 5 views
5

У меня есть указатель void, указывающий на адрес памяти. Тогда яvoid pointer = int pointer = float pointer

  • int указателя = void указателя

  • float указатель = void указателя

, а затем, разыменовать они идут получить значение.

{ 
    int x = 25; 

    void *p = &x; 
    int *pi = p; 
    float *pf = p; 
    double *pd = p; 

    printf("x: n%d\n", x); 
    printf("*p: %d\n", *(int *)p); 
    printf("*pi: %d\n", *pi); 
    printf("*pf: %f\n", *pf); 
    printf("*pd: %f\n", *pd); 

    return 0; 
} 

Выход разыменования pi (int указателя) равно 25. Однако выход разыменования pf (float указателя) 0.000. Также dereferncing pd (double pointer) выводит отрицательную долю, которая сохраняет меняется?

Почему это и связано с контентом (мой процессор немногочисленный)?

+3

Здесь есть тонна UB. Это домашняя проблема? – user3528438

+0

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

+2

, если вы ожидаете, что все указатели будут показаны 25, это не может быть правдой, прочитайте о mantissa –

ответ

6

Согласно стандарту C, вы можете конвертировать любой указатель в void * и конвертировать его обратно, он будет иметь тот же эффект.

Цитирую C11, глава §6.3.2.3

[...] Указатель на любой тип объекта может быть преобразован в указатель на void и обратно; результат равен , сравнивается с исходным указателем.

Именно поэтому, когда вы вводите указатель на пустоту в int *, снимаете ссылку и печатаете результат, он печатает правильно.

Однако стандарт не гарантирует, что вы можете разыменовать этот указатель на другой тип данных. Это, по сути, вызывает неопределенное поведение.

Так, разыменования pf или pd, чтобы получить float или double является undefined behavior, как вы пытаетесь прочитать память, выделенную для intв в float или double. Есть ясный случай mismtach, который ведет к UB.

Разрабатывать, int и floatdouble) имеет различные внутренние представления, таким образом пытаясь привести указатель на другой тип и затем попытка разыменования, чтобы получить значение в другом типе не будет работать.

Связанных, C11, глава §6.5.3.3

[...] Если операнд имеет «„указатель на тип“типа», то результат имеет тип «„типа“». Если для указателя присвоено недопустимое значение , поведение унарного оператора * равно неопределенным.

и для недопустимого значения части (курсив моего)

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

+0

Если 'p' имеет тип' void * ', то он должен быть преобразован в' float * 'и' double * '. – haccks

+1

@haccks, но попытка разыменования вызовет UB, нет? Пожалуйста, исправьте меня, если я ошибаюсь. –

+0

Я еще не уверен. глядя на стандарт. – haccks

4

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

Целые числа обычно хранятся в способе Two's complement, в основном это означает, что число хранится как одна часть. Поплавки с другой стороны хранятся другим способом, используя знак, базу и экспонента, Read here.

Таким образом, основная идея конверсии невозможна, поскольку вы пытаетесь принять число, представленное как исходные биты (для положительного), и посмотрите на него, как если бы он был закодирован по-разному, это приведет к неожиданным результатам, даже если конвертация была законной ,

2

Есть два типа УБ происходит здесь:

1) Строгое сглаживание

What is the strict aliasing rule?

«Строгий сглаживание предположение, сделанное в C (или C++) компилятора, что указатели разыменования объектов разных типов никогда не будут ссылаться на одно и то же место памяти (то есть друг на друга). «

Однако строгое наложение может быть отключено как расширение компилятора, например -fno-strict-aliasing в GCC. В этом случае ваша версия pf будет функционировать хорошо, хотя реализация определена, предполагая, что ничто другое не пошло не так (обычно float и int являются 32-разрядными и 32-разрядными, выровненными на большинстве компьютеров, обычно). Если ваш компьютер использует IEEE754 single, вы можете получить очень маленький номер denorm floating point, который объясняет результат, который вы наблюдаете.

Строгое сглаживанием является спорной особенностью последних версий C (и считается ошибкой большого количеством людей) и делает его очень трудным и более Hacky, чем раньше, чтобы сделать переосмысливать бросок (ака type punning) в С.

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

2) Память из связанных

указатель указывает на ячейку памяти, как большой, как int, но вы разыменования его как double, который, как правило, в два раза размера с int, вы в основном чтение половина double мусора из где-то в компьютере, поэтому ваш double продолжает меняться.

3

Итак ... вот, наверное, что происходит.

Однако выход разыменования пфа (поплавок указатель) 0.000

Это не 0. Это просто очень мало.

У вас есть 4-байтовые целые числа. Ваше целое выглядит в памяти ...

5  0  0  0 
00000101 00000000 00000000 00000000 

Что истолковано как float выглядит ...

sign exponent fraction 
    0 00001010 0000000 00000000 00000000 
    + 2**-117 * 1.0 

Итак, вы выводите поплавок, но это невероятно крошечные. Это 2^-117, который практически неотличим от 0.

Если вы попробуете распечатать поплавок с printf("*pf: %e\n", *pf);, тогда он должен дать вам что-то содержательное, но маленькое. 7.006492e-45

Также dereferncing pd (двойной указатель) выводит отрицательную долю, которая продолжает меняться?

Doubles - это 8-байтовые, но вы определяете только 4 байта. Изменение отрицательной фракции является результатом поиска неинициализированной памяти. Значение неинициализированной памяти является произвольным, и это нормально, когда он меняется с каждым прогоном.

+0

С этим ответом что-то не так, потому что '2^-117' не' 7e-45'. Я подозреваю, что это связано с денормализованными номерами, но я не смог выяснить детали этого поведения. Тем не менее, суть ответа (5 разборки до очень маленького поплавка). – QuestionC

+0

Отличное объяснение. +1 для представления памяти поплавков – MAA

1

Типы int, float и double имеют разные макеты памяти, представления и интерпретации.

На моей машине int составляет 4 байта, float - 4 байта, а double - 8 байт.

Вот как вы объясняете результаты, которые видите.

Отмена указателя int, очевидно, потому что исходные данные были int.

Исключая указатель float, компилятор генерирует код для интерпретации содержимого 4 байта в памяти как float. Значение в 4 байтах, если оно интерпретируется как float, дает вам 0.00. Посмотрите, как float представлен в памяти.

Производящий указатель double, компилятор генерирует код для интерпретации содержимого в памяти как double. Поскольку double больше, чем int, он обращается к 4 байтам оригинала int и дополнительно 4 байта в стеке. Поскольку содержимое этих дополнительных 4 байтов зависит от состояния стека и непредсказуемо от запуска до запуска, вы видите переменные значения, которые соответствуют интерпретации всего 8 байтов как double.

1

В дальнейшем

printf("x: n%d\n", x); //OK 
printf("*p: %d\n", *(int *)p); //OK 
printf("*pi: %d\n", *pi); //OK 
printf("*pf: %f\n", *pf); // UB 
printf("*pd: %f\n", *pd); // UB 

доступы в первые 3 printfs хороши, как вы доступ к int через тип объекта типа int.Но следующие 2 не соответствуют штрафу 6.5, 7, Выражения.

int * не совместимый тип с float * или double *. Таким образом, обращения в последних двух вызовах printf() вызывают неопределенное поведение.

C11, $ 6,5, 7 гласит:

Объект должен быть его сохраненное значение доступно только с помощью выражения Lvalue, который имеет один из следующих типов:
- тип, совместимый с эффективным тип объекта,

- квалифицированная версия типа, совместимого с эффективным типом объекта,

- тип, который является знак или без знака типа, соответствующий эффективного типа объекта,

- тип, который является знаком или без знака типа, соответствующего квалифицированного версии эффективного типа объекта,

- совокупность или объединения типа который включает один из вышеупомянутых типов среди его членов (включая рекурсивно, член объединенного или объединенного объединения) или

- тип символа.

0

Термин «С» используется для описания двух языков: один изобретенный K & R, в котором указатели идентификации местоположения физической памяти, и тот, который является производным от той, которая работает так же, в тех случаях, когда указатели либо чтения и написаны способами, которые соблюдают определенные правила, но могут вести себя произвольно, если они используются другими способами. Хотя последний язык определен стандартами, прежний язык стал популярным для программирования микрокомпьютеров в 1980-х годах.

Одним из основных препятствий для создания эффективного машинного кода из кода C является то, что компиляторы не могут определить, какие указатели могут использовать псевдонимы для переменных. Таким образом, любой временной код обращается к указателю, который может указывать на заданную переменную, генерируемый код необходим для обеспечения соответствия содержимого памяти, идентифицированного указателем и содержимым переменной. Это может быть очень дорого. Люди, пишущие стандарт C89, решили, что компиляторам следует разрешить предположить, что именованные переменные (статические и автоматические) будут доступны только с помощью указателей их типа или типов символов; люди, пишущие C99, решили добавить дополнительные ограничения для выделенного хранилища.

Некоторые компиляторы предлагают средства, с помощью которых код может гарантировать, что доступ с использованием разных типов будет проходить через память (или, по крайней мере, вести себя так, как если бы они это делали), но, к сожалению, я не думаю, что для этого существует какой-либо стандарт. C14 добавила модель памяти для использования с многопотоковой обработкой, которая должна быть способна к достижению требуемой семантики, но я не думаю, что компиляторы должны соблюдать такую ​​семантику в тех случаях, когда они могут сказать, что для внешних потоков нет доступа к чему-либо [ даже если переход через память будет необходим для достижения правильной однопоточной семантики].

Если вы используете gcc и хотите иметь семантику памяти, которая работает как K & R, используйте параметр командной строки «-fno-strict-aliasing». Чтобы сделать код эффективным, необходимо будет существенно использовать «ограничивающий» квалификатор, который был добавлен в C99.Хотя авторы gcc, похоже, больше сосредоточились на правилах псевдонимов на основе типов, чем «ограничить», последнее должно позволить более полезные оптимизации.