Почему адрес переменной-указателя печатается по-разному между двумя операторами printf без каких-либо изменений в переменной?

В приведенной ниже простой тестовой программе c два оператора printf возвращают разные значения. (проверьте последние четыре оператора printf).

int main ()
{
    char c, *cc;
    int i;
    long l;
    float f;
    c = 'Z';
    i = 15;
    l = 7777;
    f = 9999999312;
    cc = &c;
    printf("\nc = %c, cc= %u", *cc, cc);
    cc = &i;
    printf("\nc = %d, cc= %u", *cc, cc);
    cc = &l;
    printf("\nc = %ld, cc= %u", *( long* )cc, cc);
    printf("\nc = %ld, cc= %u", *cc, cc);
    cc = &f;
    printf("\nc = %f, cc= %u", *(float *)cc, cc);
    printf("\n cc= %u", cc);
    printf("\nc = %f, cc= %u", *cc, cc);
    printf("\nc = %f, cc= %u", *(float *)cc, cc);

    printf("\nc = %f, cc using pointer  = %p", *(float *)cc, cc);
    printf("\nc = %f, cc using pointer  =%p", *cc, cc);

    return 0;
}

выход : -

c = Z, cc= 755585903
c = 15, cc= 755585904
c = 7777, cc= 755585912
c = 97, cc= 755585912
c = 9999998976.000000, cc= 755585908
cc= 755585908
c = 9999998976.000000, cc= 4294967288
c = 9999998976.000000, cc= 755585908
c = 9999998976.000000, cc using pointer  = 0x7ffc37f4ace4
c = 9999998976.000000, cc using pointer  =0xfffffff8

Я запускаю его в eclipse ide и использую Ubuntu Linux.

Почему оно ведет себя иначе?


person dkm    schedule 12.03.2018    source источник
comment
УБ. По грузовику.   -  person Bathsheba    schedule 12.03.2018
comment
Вау вау. Замедлять. C нельзя узнать экспериментальным путем.   -  person Eugene Sh.    schedule 12.03.2018
comment
@MichaelWalz Я предполагаю, что он ожидает, что значения будут одинаковыми, потому что переменная cc не изменяется во время последних двух операторов printf, а напечатанное значение отличается (4294967288 против 755585908).   -  person s7amuser    schedule 12.03.2018
comment
Если я добавлю следующие две строки printf(\nc = %f, cc с использованием указателя = %p, *(float *)cc, cc); printf(\nc = %f, cc с использованием указателя =%p, *cc, cc); Я получаю следующие выходные данные c = 9999998976.000000, cc с использованием указателя = 0x7ffc37f4ace4 c = 9999998976.000000, cc с использованием указателя = 0xfffffff8, которые снова разные..   -  person dkm    schedule 12.03.2018
comment
Они отличаются, потому что спецификаторы формата не соответствуют аргументам, то есть UB.   -  person Jabberwocky    schedule 12.03.2018
comment
@MichaelWalz Спасибо за ответ. Но мне все еще не ясно. Единственная разница между последними двумя строками связана с переменной c, одна была с преобразованием типа, а другая была прямой, и на результат это не повлияло, но почему это влияет на адрес cc?   -  person dkm    schedule 12.03.2018
comment
Как только аргументы не соответствуют спецификаторам формата, вы попадаете в область неопределенного поведения. Тогда все может случиться. Но на некоторых платформах он может отображать те же значения.   -  person Jabberwocky    schedule 12.03.2018
comment
В последнем printf() вы передаете char (который преобразуется в int), а затем говорите printf() отформатировать его как double. У этого так много возможностей запутать вас, компилятор и библиотеку — это неопределенное поведение; вам повезло, что компилятор не решил переформатировать ваш жесткий диск (это неопределенное поведение — к счастью для вас, компиляторы делают это нечасто). Нет смысла спекулировать на деталях. То, что вы показываете, загружено неопределенным поведением. Все является приемлемым результатом.   -  person Jonathan Leffler    schedule 12.03.2018
comment
Знание того, как большинство компиляторов C генерирует сборку, полезно, но гораздо полезнее знать, как правильно реагировать на предупреждения компилятора C. Если ваш компилятор не выдает вам хотя бы дюжину предупреждений, то вы либо не используете его в меру своих возможностей, либо вам нужен другой компилятор.   -  person Jeff Holt    schedule 12.03.2018
comment
относительно: f = 9999999312; этот оператор присваивает int float. Предложите избежать неявного преобразования и записать оператор как: f = 9999999312.0f;   -  person user3629249    schedule 13.03.2018
comment
в отношении: cc = &i; выполняется несоответствующее присвоение из 'address ofint' being assigned to a 'pointer to char' Such oversights result in an implicit conversion. When using gcc, with the option -Wconversion' компилятор сообщит вам об этой проблеме   -  person user3629249    schedule 13.03.2018
comment
по поводу: printf("\nc = %ld, cc= %u", *cc, cc); переменная cc является указателем на char. так что это НЕ unsigned int. правильный спецификатор формата - %p, поэтому исправленное выражение будет таким: `printf(\nc = %ld, cc= %p, *cc, cc);   -  person user3629249    schedule 13.03.2018


Ответы (2)


В нескольких местах вы используете неправильный спецификатор формата для printf. В частности, в этой строке:

printf("\nc = %f, cc= %u", *cc, cc);

Первый параметр имеет тип char, но вы используете %f, который ожидает double. Кроме того, второй параметр имеет тип char *, но %u ожидает unsigned int. Использование неправильных спецификаторов формата вызывает неопределенное поведение.

При этом, вот что, скорее всего, происходит под капотом:

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

Когда вы запускаете приведенную выше команду printf, в стек помещаются и *cc, и cc, поскольку ни одно из них не является числом с плавающей запятой. Когда printf затем ищет первый параметр, он видит %f, поэтому извлекает значение из регистра с плавающей запятой. Поскольку вы фактически передали значение с плавающей запятой в предыдущий раз, когда вы вызывали printf, это значение все еще существует, и это значение оказывается тем значением, которое вы действительно хотели напечатать, так что это то, что печатается.

Затем, когда printf переходит к печати следующего параметра, он видит %u в формате, поэтому значение извлекается из стека. Это значение *cc, которое указывает на первый байт в представлении f.

Предполагая, что float является числом с плавающей запятой одинарной точности IEEE754, содержащееся в нем значение представляется как 0xf8021550. Первый байт этого 0xf8. Поскольку cc указывает на char, который в вашем случае кажется подписанным, он интерпретируется как отрицательное значение. При передаче в printf значение повышается до типа int, поэтому фактическое передаваемое значение равно 0xfffffff8, что вы и видите при печати.

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

person dbush    schedule 12.03.2018

следующий предлагаемый код:

  1. чисто компилирует
  2. не имеет сбоев из-за неправильных назначений и неверных обращений к printf()
  3. фактический вывод не передается на терминал до выхода из программы, потому что во всех строках формата для вызовов printf() отсутствует завершающий '\n'.

Примечание: предлагаемый код был скомпилирован в 64-битной системе со следующей командной строкой:

gcc -ggdb  -Wall -Wextra -Wconversion -std=gnu11 -pedantic -o "untitled2" "untitled2.c"   

а теперь предлагаемый код:

#include <stdio.h>

int main ( void )
{
    char  c;
    char *cc;
    int   i;
    long  l;
    float f;

    c = 'Z';
    i = 15;
    l = 7777;
    f = 9999999312.0f;

    cc = &c;
    printf("\nc = %c, cc= %p", *cc, cc);

    cc = (char*)&i;
    printf("\nc = %d, cc= %p", *cc, cc);

    cc = (char*)&l;
    printf("\nc = %ld, cc= %p", *( long* )cc, cc);
    printf("\nc = %ld, cc= %p", *(long*)cc, cc);

    cc = (char*)&f;
    printf("\nc = %f, cc= %p", *(float *)cc, cc);
    printf("\n cc= %p", cc);
    printf("\nc = %f, cc= %p", *(float*)cc, cc);
    printf("\nc = %f, cc= %p", *(float*)cc, cc);

    printf("\nc = %f, cc using pointer  = %p", *(float *)cc, cc);
    printf("\nc = %f, cc using pointer  = %p", *(float*)cc, cc);

    return 0;
}

В результате получается:

c = Z, cc= 0x7fffb663ae2f
c = 15, cc= 0x7fffb663ae30
c = 7777, cc= 0x7fffb663ae38
c = 7777, cc= 0x7fffb663ae38
c = 9999998976.000000, cc= 0x7fffb663ae34
 cc= 0x7fffb663ae34
c = 9999998976.000000, cc= 0x7fffb663ae34
c = 9999998976.000000, cc= 0x7fffb663ae34
c = 9999998976.000000, cc using pointer  = 0x7fffb663ae34
c = 9999998976.000000, cc using pointer  = 0x7fffb663ae34
person user3629249    schedule 13.03.2018