printf scanf %d - нули для всех кроме последнего - как исправить - несколько значений подряд

Forums:

Почему если вводить три единицы - для двух первых значений будут напечатаны нули??

#include < stdio.h >
char main (void)
{
    char banana=1;
    char choc=1;
    char apple=1;
    printf("How many bananas do you need?\r\n");
    scanf(" %d",&banana);
    printf("How many bars of chocolate do you need?\r\n");
    scanf(" %d",&choc);
    printf("How many apples do you need?\r\n");
    scanf(" %d",&apple);
    printf("Total:\r\n");
    printf("%-15s: %d\r\n","Bananas",banana);
    printf("%-15s: %d\r\n","Choc.Bars",choc);
    printf("%-15s: %d\r\n","Apples",apple);
return;
}

То есть что-то типа:

How many bananas do you need?
How many bars of chocolate do you need?
How many apples do you need?
Total:
Bananas        : 0
Choc.Bars      : 0
Apples         : 1

Если компилировать эту программу компилятором

cc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4

то такого эффекта не наблюдается. Однако при этом cc выдаёт следующие предупреждения

prog.c: In function ‘main’:
prog.c:8:2: warning: format ‘%d’ expects argument
of type ‘int *’, but argument 2 has type ‘char *’ [-Wformat=]
  scanf(" %d",&banana);
  ^
prog.c:10:5: warning: format ‘%d’ expects argument
of type ‘int *’, but argument 2 has type ‘char *’ [-Wformat=]
     scanf(" %d",&choc);
     ^
prog.c:12:5: warning: format ‘%d’ expects argument
of type ‘int *’, but argument 2 has type ‘char *’ [-Wformat=]
     scanf(" %d",&apple);

Но если компилировать эту программу более старым компилятором

cc 2.95.4,

то для первых двух переменных будут печататься нули. Почему?

Посмотрим по каким адресам загружаются переменные

    char banana=1;
    char choc=1;
    char apple=1;

Используем для этого отладчим gdb

rootmachine ~]# gdb ./prog
GNU gdb 6.8
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i586-linux"...
(gdb) list
1       #include <stdio.h>
2       int main (void)
3       {
4           char banana = 1;
5           char choc = 1;
6           char apple = 1;
7           printf("How many bananas do you need?\r\n");
8           scanf(" %d",&banana);
9           printf("How many bars of chocolate do you need?\r\n");
10          scanf(" %d",&choc);
(gdb) break 9
Breakpoint 1 at 0x8048486: file prog.c, line 9.
(gdb) run
Starting program: /root/prog
How many bananas do you need?
13
 
Breakpoint 1, main () at prog.c:9
9           printf("How many bars of chocolate do you need?\r\n");
(gdb) print banana
$1 = 13 '\r'
(gdb) print &banana
$2 = 0xbffff6f7 "\r"
(gdb) print &choc
$3 = 0xbffff6f6 "\001\r"
(gdb) print &apple
$4 = 0xbffff6f5 "\001\001\r"
(gdb)     

Строки 32, 34 и 36 показывают, что наши переменные размещаются в памяти последовательно:

0xbffff6f7     banana
0xbffff6f6     choc
0xbffff6f5     apple

В машинах архитектуры x86 младший байт целочисленных типов
(например, 32 разрядные int в си, или dword в ассемблере) размещается по младшему адресу.
Адрес переменной в Си есть адрес её младшего байта.
Переменная типа char занимает в памяти один байт, а переменная типа int занимает 4 байта.

%d указывает на тип int, то есть на 4 байта.

Если будет выполнена строка
scanf(" %d",&banana);

то в память начиная с адреса 0xbffff6f7, будут записаны 4 байта.
Это значит, что будут изменены ячейки по адресам

0xbffff6fa
0xbffff6f9
0xbffff6f8
0xbffff6f7

После выполнения строки

    scanf(" %d",&choc);

в память, начиная с адреса 0xbffff6f6, будут записаны 4 байта.
Это значит, что будут изменены ячейки по адресам

0xbffff6f9
0xbffff6f8
0xbffff6f7
0xbffff6f6

Получается, что если мы будем вводить, например 12, то младший байт того значения типа int,
которое будет записываться начиная с адреса 0xbffff6f6 (это адрес переменной banana),
будет содержать значение 12, а старшие три байта --- нули.

Поэтому теперь байт по адресу &banana имеет значение ноль.

После выполнения строки
scanf(" %d",&apple);

в память, начиная с адреса 0xbffff6f5, будут записаны 4 байта.
Это значит, что будут изменены ячейки по адресам
0xbffff6f9
0xbffff6f8
0xbffff6f7
0xbffff6f6

Получается, что мы уничтожаем и значение переменной choc.

Тем не менее, когда будет выполняться строка

printf("%-15s: %d\r\n","Apples",apple);

будет выводиться значение именно одного байта по адресу &apple.

В обоих системах, и в старой с компилятором cc 2.95.4, и в новой с cc (Ubuntu 4.8.4-2ubuntu1~14.04) 4.8.4, это значение, кажется, будет интерпретировано как дополнительный код.
Так как когда я ввожу для apple значение 128, то получаю в младшем байте

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

Кстати, компилятор cc 2.95.4 выдаёт ещё такое предупреждение:

prog.c:3: warning: return type of 'main' is not 'int'
vedro-compota's picture

классный ответ)

_____________
матфак вгу и остальная классика =)

Нужно изменить тип переменных с char на int.