Неожиданное поведение при печати 4-байтового целого байта за байтом - PullRequest
1 голос
/ 09 января 2010

У меня есть этот пример кода для преобразования 32-битных целых чисел в IP-адреса.


#include <stdio.h>
int main()
{
 unsigned int c ;
 unsigned char* cptr  = (unsigned char*)&c ;
 while(1)
 {
  scanf("%d",&c) ;
  printf("Integer value: %u\n",c);
  printf("%u.%u.%u.%u \n",*cptr, *(cptr+1), *(cptr+2), *(cptr+3) );
 }
}

Этот код дает неправильный вывод для ввода 2249459722. Но когда я заменяю

scanf("%d",&c) ;
на
scanf("%u",&c) ;
Вывод получается правильным.

P.S : Я знаю о inet_ntop и inet_pton.
Я ожидаю ответов, отличных от предлагаемых.

Ответы [ 4 ]

13 голосов
/ 09 января 2010

Вы кодируете ' греховно ' (совершая ряд ошибок, которые рано или поздно причинят вам боль - в основном раньше). Во-первых, вы предполагаете, что целое число имеет правильный порядковый номер. На некоторых машинах вы будете неправы - либо на машинах Intel, либо на машинах PowerPC или SPARC.

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


Вот моя модифицированная версия вашего кода - вместо запроса ввода она просто принимает указанное вами значение.

#include <stdio.h>
int main(void)
{
    unsigned int c = 2249459722;
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
    return(0);
}

При компиляции на моем Mac (Intel, little-endian) вывод:

Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 

Когда компилируется на моем Sun (SPARC, big-endian), вывод:

Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 134.20.8.10 

(Используя GCC 4.4.2 на SPARC, я получаю предупреждение:

xx.c:4: warning: this decimal constant is unsigned only in ISO C90

Использование GCC 4.2.1 на Mac - с большим количеством включенных предупреждений (gcc -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes -Werror) - я не получаю это предупреждение, что интересно.) Я могу удалить это, добавив суффикс U к целочисленной константе .


Другой способ взглянуть на проблемы иллюстрируется следующим кодом и чрезвычайно суетливыми настройками компилятора, показанными выше:

#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722;

    printf("Direct operations:\n");
    print_value(c);

    printf("Indirect operations:\n");
    if (sscanf("2249559722", "%d", &c) != 0)
        printf("Conversion failed for %s\n", str);
    else
        print_value(c);
    return(0);
}

Не удается скомпилировать (из-за настройки -Werror) сообщение:

cc1: warnings being treated as errors
xx.c: In function ‘main’:
xx.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’

Удалите настройку -Werror, и она скомпилируется, но затем отобразит следующую проблему, которая у вас возникла - не проверять наличие ошибок в функциях, которые могут дать сбой:

Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Conversion failed for 2249459722

По сути, функция sscanf() сообщает, что ей не удалось преобразовать строку в целое число со знаком (поскольку значение слишком велико, чтобы уместиться - см. Предупреждение из GCC 4.4.2), но ваш код не проверял наличие ошибка возврата из sscanf(), поэтому вы использовали любое значение, оставленное в c на тот момент.

Итак, в вашем коде много проблем:

  • Предполагается, что определенная архитектура (с прямым порядком байтов вместо того, чтобы признавать, что байты с прямым порядком байтов также существуют).
  • Он не компилируется чисто при использовании компилятора с большим количеством предупреждений - по уважительной причине.
  • Он не проверяет, что функции, которые могут выйти из строя, действительно были успешными.

Комментарий Алока

Да, проверка на sscanf() неверна. Вот почему у вас есть обзоры кода, а также почему это помогает опубликовать код, который вы тестируете.

Теперь я немного озадачен - получаю последовательное поведение, которое не могу сразу объяснить. С очевидной ревизией (тестирование на MacOS X 10.6.2, GCC 4.2.1, 32-битные и 64-битные компиляции) я получил один не очень вменяемый ответ. Когда я переписываю более модульно, я получаю здравый ответ.

+ cat yy.c
#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722;

    printf("Direct operations:\n");
    print_value(c);

    printf("Indirect operations:\n");
    if (sscanf("2249559722", "%d", &c) != 1)
        printf("Conversion failed for %s\n", str);
    else
        print_value(c);
    return(0);
}


+ gcc -o yy.32 -m32 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes yy.c
yy.c: In function ‘main’:
yy.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’


+ ./yy.32
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Integer value:  2249559722
Integer value:  0x86158EAA
Dotted decimal: 170.142.21.134 

У меня нет хорошего объяснения значения 170.142.21.134; но это соответствует моей машине, на данный момент.

+ gcc -o yy.64 -m64 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes yy.c
yy.c: In function ‘main’:
yy.c:20: warning: format ‘%d’ expects type ‘int *’, but argument 3 has type ‘unsigned int *’


+ ./yy.64
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations:
Integer value:  2249559722
Integer value:  0x86158EAA
Dotted decimal: 170.142.21.134 

То же значение - даже в 64-битной версии вместо 32-битной. Возможно, проблема в том, что я пытаюсь объяснить неопределенное поведение, которое по определению более или менее необъяснимо (необъяснимо).

+ cat xx.c
#include <stdio.h>

static void print_value(unsigned int c)
{
    unsigned char* cptr  = (unsigned char*)&c;
    printf("Integer value:  %10u\n", c);
    printf("Integer value:  0x%08X\n", c);
    printf("Dotted decimal: %u.%u.%u.%u \n", *cptr, *(cptr+1), *(cptr+2), *(cptr+3));
}

static void scan_value(const char *str, const char *fmt, const char *tag)
{
    unsigned int c;
    printf("Indirect operations (%s):\n", tag);
    fmt = "%d";
    if (sscanf(str, fmt, &c) != 1)
        printf("Conversion failed for %s (format %s \"%s\")\n", str, tag, fmt);
    else
        print_value(c);
}

int main(void)
{
    const char str[] = "2249459722";
    unsigned int c = 2249459722U;

    printf("Direct operations:\n");
    print_value(c);
    scan_value(str, "%d", "signed");
    scan_value(str, "%u", "unsigned");

    return(0);
}

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

+ gcc -o xx.32 -m32 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes xx.c


+ ./xx.32
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (signed):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (unsigned):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 

Результаты согласуются здесь.

+ gcc -o xx.64 -m64 -std=c99 -pedantic -Wall -Wshadow -Wpointer-arith -Wstrict-prototypes -Wmissing-prototypes xx.c


+ ./xx.64
Direct operations:
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (signed):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134 
Indirect operations (unsigned):
Integer value:  2249459722
Integer value:  0x8614080A
Dotted decimal: 10.8.20.134

И это то же самое, что и 32-разрядный корпус. Я официально ошеломлен. Основные наблюдения остаются точными - будьте внимательны, учитывайте предупреждения компилятора (и извлекайте предупреждения компилятора) и не предполагайте, что «весь мир работает на чипах Intel» (раньше это было «не предполагайте, что весь мир VAX ", давным-давно!).

5 голосов
/ 09 января 2010

% d для целых чисел со знаком

% u для целых чисел без знака

Edit:

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

#include <stdio.h>
int main()
{
 unsigned int c ; 
 unsigned char* cptr  = (unsigned char*)&c ;
 while(1)
 {
  scanf("%d",&c) ;
  printf("Signed value: %d\n",c);
  printf("Unsigned value: %u\n",c);
  printf("%u.%u.%u.%u \n",*cptr, *(cptr+1), *(cptr+2), *(cptr+3) );
 }
}

Что происходит, когда вы вводите число больше INT_MAX, самый левый бит равен 1. Это означает, что это целое число со знаком и отрицательным значением. Затем число интерпретируется как дополнение к двум

1 голос
/ 10 января 2010

Правильный безопасный способ записи это

printf("Dotted decimal: %u.%u.%u.%u \n", (c >> 24) & 0xff, (c >> 16) & 0xff, (c >> 8) & 0xff, (c >> 0) & 0xff);
1 голос
/ 10 января 2010

Чтобы ответить на ваш главный вопрос:

scanf("%d", &c);
Поведение

scanf() не определено, когда преобразуемый ввод не может быть представлен типом данных. 2249459722 на вашем компьютере не подходит для int, поэтому scanf() может делать все что угодно, включая хранение мусора в c.

В C тип int гарантированно сможет хранить значения в диапазоне от -32767 до +32767. unsigned int - это гарантированные значения от 0 до 65535. Таким образом, 2249459722 не обязательно должен соответствовать даже unsigned int. Однако unsigned long может хранить значения до 4294967295 (2 32 & minus; 1), поэтому вы должны использовать unsigned long:

#include <stdio.h>
int main()
{
    unsigned long c ;
    unsigned char *cptr  = (unsigned char*)&c ;
    while(1)
    {
        if (scanf("%lu", &c) != 1) {
            fprintf(stderr, "error in scanf\n");
            return 0;
        }
        printf("Input value: %lu\n", c);
        printf("%u.%u.%u.%u\n", cptr[0], cptr[1], cptr[2], cptr[3]);
    }
    return 0;
}

Если у вас есть компилятор C99, вы можете #include <inttypes.h>, а затем использовать uint32_t вместо unsigned long. scanf() вызов становится scanf("%" SCNu32, &c);

...