странная разница между указателем типа int и указателем с плавающей точкой - PullRequest
2 голосов
/ 01 октября 2011

см. Мои коды ниже

#include <stdio.h>
#include <stddef.h>

typedef struct _node
{
int a;
char *s;
}Node, *nodePtr;

int main(int argc, char *argv[])
{
char *str = "string"; /*str points to satic storage area*/
Node nd;
nodePtr pNode = NULL;
size_t offset_of_s = offsetof(Node,s);

nd.a = 1;
nd.s = str;

pNode = &nd;

    /*Get addr of s, cast it to a different data types pointer, then de-reference it*/

/*this works, print "string"*/
printf("%s\n", *(int*)((char*)pNode + offset_of_s));

/*this sucks, print (null)*/
printf("%s\n", *(float*)((char*)pNode + offset_of_s));

return 0;
} 

я пытаюсь получить адрес члена s структуры Node, приведение к типам данных не менее 4 байтов ( 4 байт). это ширина указателя на моем компьютере), затем отмените указатель в качестве аргумента на printf .

я думаю, что результатом двух printfs должно быть то же самое , но на втором выводится "(null)" .

float и int имеют одинаковую ширину байта на моей машине, является ли внутреннее разное представление двух типов, которые вызывают это?

спасибо заранее!

Ответы [ 4 ]

6 голосов
/ 01 октября 2011

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

C99-TC3, §7.19.6.1/9

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

Однако, если вас интересует причина, по которой вы наблюдаете поведение, то, скорее всего, ваш компилятор является одним из тех, которые передают значения с плавающей запятой в printf () в регистрах ЦП с плавающей запятой.(GNU и CLang делают это, например).Второй вызов printf поместил разыменованное значение в регистр с плавающей запятой, но printf, увидев спецификатор преобразования %s, посмотрел на регистр, где должен был быть передан char*, вероятно, регистр общего назначения,который оказался нулевым в вашем случае.

PS: Вот что GCC 4.6.1 делает из этого на моем linux

main:
    pushq   %rbx
    leal    .LC0(%rip), %ebx
    movl    $.LC1, %esi
    subq    $16, %rsp
    movl    %ebx, %edx
    movl    $1, %edi
    movq    $.LC0, 8(%rsp)
    xorl    %eax, %eax
    call    __printf_chk

    movd    %ebx, %xmm0
    movl    $.LC1, %esi
    movl    $1, %edi
    movl    $1, %eax
    unpcklps    %xmm0, %xmm0
    cvtps2pd    %xmm0, %xmm0 # this is where your value went
    call    __printf_chk     # is NOT gonna read from xmm0!

    addq    $16, %rsp
    xorl    %eax, %eax
    popq    %rbx
    ret

Та же история с clang 2.9

    ...
    movl    $.L.str, %ebx
    xorb    %al, %al
    movl    $.L.str1, %edi     # .L.str1 is your format "%s\n"
    movl    $.L.str, %esi      # .L.str  is your static "string"
    callq   printf

    movd    %ebx, %xmm0        # your value is in xmm0 again
    cvtss2sd    %xmm0, %xmm0   # promoted to double, but still in xmm0
    movb    $1, %al
    movl    $.L.str1, %edi
    callq   printf             # printf has no idea
2 голосов
/ 01 октября 2011

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

Я бы предположил, что вы ожидаете, что переменные аргументы будут скопированы в "массив переменных аргумента""своего рода (стековый фрейм?) как блоки сырой памяти, независимо от их семантики, зависящей от типа.По этой причине вы, очевидно, считаете, что аргумент int должен передаваться точно так же, как аргумент float, поскольку оба типа имеют одинаковый размер на вашей платформе.

Это предположение абсолютно необоснованнои неверно.Фактически в этом случае printf передается значений рассматриваемых аргументов, и, поскольку эти значения имеют совершенно разную семантику, зависящую от типа, их можно легко передать совершенно разными способами.Излишне говорить, что поведение вашего кода не определено по нескольким причинам.

Одна базовая вещь, которую вам необходимо понять в этом случае, состоит в том, что совершенно невозможно передать значение float в качестве переменнойпараметр вариационной функции.Все значения float автоматически передаются в значения double перед передачей, как того требует спецификация языка.(То же самое относится к значениям char и short, которые всегда сначала переводятся в int.) Учитывая, что в вашем случае значение float было получено путем переинтерпретации памяти, занимаемой объектом указателя, а затем повышено доdouble, неудивительно, что результаты, которые вы наблюдаете, не имеют никакого смысла.

Еще одна базовая вещь, которую вам нужно понять, что реинтерпретация памяти, занятой объектом одного типа и объектом другого типа, недопускается языком C (в том смысле, что результирующее поведение не определено).Вам не разрешается интерпретировать память, занятую объектом указателя, как объект int.И это именно то, что вы пытаетесь сделать.Даже первый из ваших printf, который якобы «работает как положено», делает это только случайно.

1 голос
/ 01 октября 2011

Да.Внутреннее представление с плавающей запятой и целого числа в двоичном коде значительно отличается.

0 голосов
/ 01 октября 2011

Если вы хотите адрес, используйте спецификатор формата "% p" с printf (). Он был в C с K & R2 и, может быть, раньше.

...