Выполняя упражнение K&R, я сталкиваюсь с поведением, которое не могу понять и для которого я не могу найти никаких решений здесь, в SOF:

Программа (преобразование базовых 10 int в базовые строки n ) отлично работает с моими дополнительными операторами printf(). Как только я начинаю их удалять или комментировать, начинается неожиданное поведение

Полный код:

#include <stdio.h>
#include <string.h>
#include <assert.h>

#define MAXLINE 1000

char numtochar(unsigned n);
void reverse(char s[]);
void itob(int n, char s[], unsigned b);

int main() {
    // test numtochar
    assert(numtochar(1) == '1');
    assert(numtochar(11) == 'b');
    assert(numtochar(61) == 'Z');
    assert(numtochar(62) == '+');
    // test reverse
    char t[] = "TestiNg";
    reverse(t);
    assert(!strcmp(t, "gNitseT"));
    // test itob
    printf("if this is commented out, it will segfault\n");
    char s[MAXLINE];
    itob(10, s, 10);
    printf("%s\n", s);
    assert(strcmp(s, "10") == 0);
    itob(11, s, 2);
    printf("%s\n", s);
    assert(strcmp(s, "1011") == 0);
    itob(100, s, 8);
    printf("%s\n", s);
    assert(strcmp(s, "144") == 0);
    itob(1337, s, 32);
    printf("%s\n", s);
    assert(strcmp(s, "19p") == 0);
    itob(127, s, 64);
    printf("%s\n", s);
    assert(strcmp(s, "1/") == 0);
    return 0;
}

/* This numbering is not standard base-64, but will be consistent so long
 * as base <= 64
 * 0..63 => 0..9a..zA..Z+/
 */
char numtochar(unsigned n) {
    assert(n < 64);
    if (n < 10)
        return ('0' + n);
    else if (n >= 10 && n < 36)
        return ('a' + (n - 10));
    else if (n >= 36 && n < 62)
        return ('A' + (n - 36));
    else if (n == 62)
        return '+';
    else if (n == 63)
        return '/';
}

void reverse(char s[]) {
    int c, i, j;
    for (i=0, j=strlen(s)-1; i < j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
    return;
}

void itob(int n, char s[], unsigned b) {
    assert(b <= 64);
    int c, i, sign;
    if ((sign = n) < 0)
        n = -n;
    do {
        s[i++] = numtochar(n % b);
    } while ((n /= b) != 0);
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse(s);
    return;
}

Как показано ниже, если я запускаю со всеми операторами printf, он работает должным образом. Если я закомментирую первый оператор, он будет работать нормально один раз, но не снова.

user@laptop:~/git/ansi_c/ch3 $ make ex05.o # no printf statements commented
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
if this is commented out, it will segfault
10
1011
144
19p
1/
user@laptop:~/git/ansi_c/ch3 $ make ex05.o # comment out first printf statement
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
10
1011
144
19p
1/
user@laptop:~/git/ansi_c/ch3 $ make ex05.o # resave after no changes
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
Segmentation fault (core dumped)

Если же я раскомментирую первый оператор printf и закомментирую второй (printf("%s\n", s);), результат itob больше не будет передавать оператор assert.

user@laptop:~/git/ansi_c/ch3 $ make ex05.o # uncomment first printf, comment second
gcc -o ex05.o ex05.c
user@laptop:~/git/ansi_c/ch3 $ ./ex05.o
if this is commented out, it will segfault
101101
ex05.o: ex05.c:33: main: Assertion `strcmp(s, "1011") == 0' failed.
Aborted (core dumped)

Версия gcc - 7.2.1

Если я удалю все операторы printf, это также приведет к сбою. Поскольку я новичок в C, я не уверен, где я мог нераспределить память, если это действительно проблема, поскольку все подобные вопросы, которые я видел, касались использования malloc.

1
user8505916 2 Янв 2018 в 14:06

2 ответа

Лучший ответ

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

void itob(int n, char s[], unsigned b) {
    assert(b <= 64);
    int c, i, sign;
    if ((sign = n) < 0)
        n = -n;
    do {
        s[i++] = numtochar(n % b);
    } while ((n /= b) != 0);
    if (sign < 0)
        s[i++] = '-';
    s[i] = '\0';
    reverse(s);
    return;
}

Какое значение имеет i в начале? Это может быть 0, но на самом деле это будет то случайное значение, которое случайно оказалось в памяти в то время. Вот почему код без комментариев меняет ситуацию, поскольку влияет на значение i.

Просто изменив его, чтобы начать со значения 0, вы решите проблему. Также удален c, поскольку он не используется

int i = 0, sign;

Хороший инструмент - valgrind - он скажет вам, где происходят повреждения и утечки памяти.

4
Chris Turner 2 Янв 2018 в 11:20

Запуск gdb в опубликованном коде приводит к:

Примечание: «untitled2» - это имя, которое я дал вашей программе.

gdb untitled2

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 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 "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from untitled2...done.

(gdb) br main
Breakpoint 1 at 0x4008e1: file untitled2.c, line 12.

(gdb) r
Starting program: /home/rkwill/Documents/forum/untitled2 

Breakpoint 1, main () at untitled2.c:12
12  {

(gdb) c
Continuing.
if this is commented out, it will segfault

Program received signal SIGSEGV, Segmentation fault.
0x0000000000400d2a in itob (n=10, s=0x7fffffffda80 "\264\332\377\377\377\177", 
    b=10) at untitled2.c:103
103         s[i++] = numtochar(n % b);
(gdb) 

Который находится в этом блоке кода:

do
{
    s[i++] = numtochar(n % b);
} while ((n /= b) != 0);

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

Другими словами, программа содержит неопределенное поведение, и именно это вызвало ошибку seg.

0
user3629249 4 Янв 2018 в 17:46