LINUX.ORG.RU

Загадки при считывании scanf'ом в C

 ,


1

1

Добрый день. Есть три куска кода:

#include <stdio.h>

int main(void)
{
    unsigned long N;
    char K;

    scanf("%u", &N);
    printf("%u", N);

    return 0;
}
#include <stdio.h>

int main(void)
{
    unsigned long N;
    char K;

    scanf("%u", &N);
    scanf("%d", &K);
    printf("%u", N);

    return 0;
}
#include <stdio.h>

int main(void)
{
    unsigned long N;
    int K;

    scanf("%u", &N);
    scanf("%d", &K);
    printf("%u", N);

    return 0;
}
При вводе 58 первая программа выводит 58. При вводе 58<Enter>5 вторая программа выводит 0. При вводе 58<Enter>5 третья программа выводит 58. gcc version 4.9.2 (Debian 4.9.2-10)

Пожалуйста, помогите понять, что происходит. Заранее спасибо.

★★

Пожалуйста, помогите понять, что происходит.

Ты scanf просишь прочитать число одного размера, а подсовываешь буфер другого размера. Когда читаешь int в то место, где лежит char, вылазишь за границы буфера (в этом случае — в буфере один элемент) и портишь данные в соседнем участке памяти, где лежит другая локальная переменная.

i-rinat ★★★★★
()
$ gcc -g -O0 -fsanitize=address 2.c
$ ./a.out 
58
5
=================================================================
==30212==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd00578cc1 at pc 0x7f03b164e7a6 bp 0x7ffd00578b50 sp 0x7ffd00578300
WRITE of size 4 at 0x7ffd00578cc1 thread T0
    #0 0x7f03b164e7a5  (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x4a7a5)
    #1 0x7f03b164f401 in __isoc99_vscanf (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x4b401)
    #2 0x7f03b164f4e7 in __interceptor___isoc99_scanf (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x4b4e7)
    #3 0x4009a6 in main /tmp/1/2.c:9
    #4 0x7f03b12836ff in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x206ff)
    #5 0x400818 in _start (/tmp/1/a.out+0x400818)

Address 0x7ffd00578cc1 is located in stack of thread T0 at offset 33 in frame
    #0 0x4008f5 in main /tmp/1/2.c:4

  This frame has 2 object(s):
    [32, 33) 'K' <== Memory access at offset 33 overflows this variable
    [96, 104) 'N'
HINT: this may be a false positive if your program uses some custom stack unwind mechanism or swapcontext
      (longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x4a7a5) 
Shadow bytes around the buggy address:
  0x1000200a7140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a7150: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a7160: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a7170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a7180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000200a7190: 00 00 00 00 f1 f1 f1 f1[01]f4 f4 f4 f2 f2 f2 f2
  0x1000200a71a0: 00 f4 f4 f4 f3 f3 f3 f3 00 00 00 00 00 00 00 00
  0x1000200a71b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a71c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a71d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000200a71e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==30212==ABORTING
$
i-rinat ★★★★★
()

Во всех примерах формат для long тоже не тот, должен быть %lu либо тип надо поменять на unsigned int. А вообще -Wall при компиляции (точнее -Wformat) будет ругаться на такое.

xaizek ★★★★★
()

scanf - зло! Есть нормальные функции для этого: strtol, strtod и т.п.

anonymous
()

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

anonymous
()
Ответ на: комментарий от Norong

для считывания char надо было использовать %hhd?

Если ты число хочешь считать, то по манам получается, что да. Если символ, то нужно %c.

Вот, кстати, за всё то время, пока я код пишу, мне кроме %f, %lf и %d в scanf ничего не понадобилось. Как только возникает какая-то потребность в более сложных форматах, написать парсер проще, чем выяснять подробности работы scanf. И ещё анализаторы часто на него ругаются. Это минное поле.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Вот, кстати, за всё то время, пока я код пишу, мне кроме %f, %lf и %d в scanf ничего не понадобилось.

Даже "%"SCNu64, например, не понадобилось?

Sorcerer ★★★★★
()
Ответ на: комментарий от Sorcerer

SCNu64

Да вот как-то не пришлось. Спасибо за SCNxxx, я и не задумывался, что такие макросы бывают.

i-rinat ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.