LINUX.ORG.RU

Ошибки при сборке тренировочного задания по C

 , , , ,


1

2

Есть код на C, для выполнения тренировочного задания: напишите программу для вывода всех строк входного потока, имеющих длину более 80 символов. Вот код: http://ix.io/12jI

Собираю с помощью GCC, вот выхлоп "cc -v"

Using built-in specs.
COLLECT_GCC=cc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/4.9/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Debian 4.9.2-10' --with-bugurl=file:///usr/share/doc/gcc-4.9/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.9 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.9 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.9-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.9-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.9-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --with-arch-32=i586 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.9.2 (Debian 4.9.2-10)
Получаю ошибки:
hw.c:5:5: error: conflicting types for ‘getline’
 int getline(char line[], int limit);
     ^
In file included from hw.c:1:0:
/usr/include/stdio.h:678:20: note: previous declaration of ‘getline’ was here
 extern _IO_ssize_t getline (char **__restrict __lineptr,
                    ^
hw.c:16:5: error: conflicting types for ‘getline’
 int getline(char line[], int limit) {
     ^
In file included from hw.c:1:0:
/usr/include/stdio.h:678:20: note: previous declaration of ‘getline’ was here
 extern _IO_ssize_t getline (char **__restrict __lineptr,
Не могу понять, что не так. Пожалуйста, помогите разобраться.

★★
Ответ на: комментарий от Norong

В выводе всё равно иногда появляется мусор. Мне кажется, это из-за того, что в 58 строке делаю pointer указателем на то, что после p->names, не выделив прежде это место.

realloc() ещё нужен.

В моём представлении, это должно было помочь, но почему-то ошибка никуда не делась:

Так нельзя с значением void ничего делать, только с указателями на него. Без одного разименования должно работать:

t = (void **) *((void **) t + ARRSIZE - 1);

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

Про realloc примерно понял, но этот путь опробую тогда, когда в учебнике до него дойду, а то нечестно как-то выходит.

Что касается t, то я разобрался в сути; мне действительно надо было остановиться на одно разыменование раньше. Я понял так, что *((void **) t + ARRSIZE - 1) — это массив (=указатель на массив=указатель на его нулевой элемент), а с ещё одним разыменованием - уже сам нулевой элемент этого массива, т.е. не то, что мне нужно. Однако мне не до конца ясно, почему такое не прокатило. Если я правильно понял происходящее, и два разыменования дают мне нулевой элемент массива (это void *), то он должен бы в t хорошо поместиться. Или тут мешает (void **)?

Итак, t у меня теперь - массив. И дальше облом:

6.2.c:65:14: warning: dereferencing ‘void *’ pointer
             t[p->count] = malloc(sizeof(word));
              ^
6.2.c:65:13: error: invalid use of void expression
             t[p->count] = malloc(sizeof(word));
             ^
Если в t у меня указатель на нулевой элемент массива, то t[p->count] должен бы быть элементом с номером p->count, т.е. void *. Но не получается :(

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

Там массив указателей на void. Т.е. тип элемента это void*. А для работы с самим массивом надо ещё одну * добавить и получается void**. Сейчас t не работает потому что он должен быть типа void**, а не void*, массивов void'ов не может существовать (это, кстати, нельзя сделать даже в параметрах функции, т.е. void func(void arr[]) не будет работать).

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

t = (void **) p->names;

p->names — имя массива с элементами void*, т.е. t становится void**

t = (void **) *((void **) t + ARRSIZE - 1);

t + ARRSIZE - 1 — по-прежнему void**, указатель на последний элемент массива, void*. После одного разыменования получается void* - сам элемент массива. Но хотя массив объявлен, как состоящий из void*, этот конкретный элемент содержит указатель на ещё один такой же массив из void*, т.е. этот элемент - void**. С помощью (void **) я говорю, что это выражение - суть void**, и присваиваю в t. t должен void** получится же, или я чего-то не понимаю?

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

Я подумал, что в переменную типа void* можно запихнуть указатель на что угодно - в том числе, и на такой же void* (и получить void**). Это не так?

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

Так, но только для хранения. Для работы с данными нужны другие типы. В данном случае void** в качестве типа переменной. Как иначе компилятору знать что в

void *p = ...;
p[30]
p[30] должно вернуть void*, а не, скажем, double.

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

Большое спасибо, понял! Сейчас попробую)

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

Сделал void **t.

Теперь у меня осталась единственная ошибка, вот в этом месте:

t[p->count] = malloc(sizeof(p->names));
t = *(t[p->count]);
t[p->count] - это последний элемент массива из void*, и я выделяю место под ещё один такой массив, записываю указатель в этот элемент. Теперь t[p->count] - указатель на void**. Вроде, так можно.

Разименовываю его до просто void** (массива) и присваиваю t.

6.2.c:71:17: warning: dereferencing ‘void *’ pointer
             t = *(t[p->count]);
                 ^
6.2.c:71:15: error: void value not ignored as it ought to be
             t = *(t[p->count]);
               ^

И ещё:

Для работы с данными нужны другие типы.

А в таких случаях разве не должно помогать принудительное приведение типов?

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

Разименовываю его до просто void** (массива) и присваиваю t.

А в таких случаях разве не должно помогать принудительное приведение типов?

Должно, но его ведь нет.

Тип t[p->count] это void*, тип *t[p->count] это void (и так делать нельзя), а нужно (void **)t[p->count].

void* это только для передачи/хранения. Для работы нужен хотя бы void**. Компилятор эти преобразования автоматически делает только в сторону void* при этом реальный тип он больше не знает, обратно надо конвертировать самому.

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

Т.е. ситуация такова: в *void можно положить любой указатель и считать оттуда тоже можно любой указатель, но если я попробую разыменовать переменную, имеющую тип *void, то даже если фактически я туда записал **void, компилятор воспримет её так, как она была объявлена, и получится обращение к void, чего делать нельзя. Таким образом, везде, где фактический тип отличается от того, с которым переменная объявлена, следует выполнять принудительное приведение типов (и тогда я могу переменную объявлять хоть как void*, хоть как void**). Я правильно уловил суть?

Norong ★★
() автор топика
Последнее исправление: Norong (всего исправлений: 1)
Ответ на: комментарий от xaizek

У меня тут в одном месте segfault получается.

            t[p->count] = malloc(sizeof(p->names));
            ++(p->arrays);
            t = *((void ***) t[p->count]);
            *t = malloc(strlen(word) + 1);
            strcpy(*t, word);
Это выделение нового массива и помещение в его первый элемент указатель на строку.

  • t - массив, t[p->count] - его элемент типа void *. Туда помещается указатель на void**.
  • Простое приращение переменной
  • t[p->count] теперь - указатель на void**, т.е, void***. Я указываю это явно и разименую. Получаю тот самый массив, который выделен в первой строке. Присваиваю это значение t. t теперь - void**, указатель на нулевой элемент массива из void*
  • Тут segfault. *t - нулевой элемент массива из *void, я выделяю место под слово, получаю указатель на это место (типа char*) и записываю в *t.
  • Копирую туда слово.

Не подскажешь, что не так? Совсем не догоняю :(

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

Это

t = *((void ***) t[p->count]);
должно быть либо
t = *((void ***) &t[p->count]);
либо (хотя касты эти можно и не писать, главное чтобы тип t был правильный).
t = (void **) t[p->count];

В t[p->count] лежит указатель и это выражение вернёт его значение, если это значение трактовать как void*** и разименовать, то это уже будет трактовка первого элемента того участка памяти как указателя, а он указывает непонятно куда.

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

Я увидел ситуацию так: t[p->count] - это void***, указатель на void**, указатель на указатель на нулевой элемент массива из void*.

В этом коде

t = *((void ***) t[p->count]);

происходит следующее: я обозначаю, что t[p->count] - это void*** (потому что иначе компилятор бы подумал, что это лишь void*), при разыменовании компилятор начинает с самого «глубокого» уровня, и как указатель трактуется тот самый нулевой элемент массива из void*. В итоге, то, на что он указывает, записывается в t, а указывает он непонятно куда.

В этом коде:

t = (void **) t[p->count];

по-видимому, приведение к типу void** оставляет два «последних» уровня указателей, отбрасывая один «верхний»: от t[p->count] остаётся void**: указатель на нулевой элемент массива из void*. Его я присваиваю t, всё хорошо.

В этом коде:

t = *((void ***) &t[p->count]);

операция & даёт мне адрес t[p->count] (это void****), но я плохо понимаю, что получается из приведения получившегося к типу void***. Если верно моё предположение о том, что приведение к void с меньшим числом звёздочек оставляет последние уровни указателей, то от приведения к void*** должно получится то же t[p->count] что и было.

Похоже, моё понимание происходящего несовершенно. Не мог бы ты его подкорректировать, если тебе не сложно?

Norong ★★
() автор топика
Последнее исправление: Norong (всего исправлений: 2)
Ответ на: комментарий от Norong

t[p->count] - это void***, указатель на void**, указатель на указатель на нулевой элемент массива из void*.

Но указатель на массив это указатель на его первый элемент, поэтому там просто указатель на нулевой элемент массива, т.е. void**. Там именно этот адрес и хранится, а не адрес того, где этот адрес хранится. Самого верхнего указателя нет.

то от приведения к void*** должно получится то же t[p->count] что и было.

Ну да. Там ведь и хранится уже готовое значение, его надо только привести к типу без каких-либо разименований.

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

Снова здравствуй!

Я тут столкнулся с сегфолтом, с которым у меня не вышло разобраться. Не поможешь с ним справиться?

Вот код: https://gist.github.com/anonymous/e368e439c0dec05d53a02f3b01a79dfb

Сегфолт возникает на строке 82 (сейчас она закомментирована, потому что без неё код работает). Тут связанные списки, и чтобы было удобнее написать функцию undef (со строки 91, служит для того, чтобы отменять определение), я решил в каждом блоке списка (содержащем изначально имя, определение и указатель на следующий блок) сохранять также указатель на предыдущий блок. Участок кода (строки [80-83])

        np->next = hashtab[hashval];
        np->prev = NULL;
        /* (np->next)->prev = np; */
        hashtab[hashval] = np;

имеет следующий смысл: в hashtab[hashval] указатель на первый блок списка. Я записываю указатель на этот блок в поле next текущего блока. Поле prev текущего блока делаю равным null. В поле prev следующего блока (на него указывает np->next) записываю np - указатель на текущий блок. В hashtab[hashval] записываю указатель на текущий блок. Таким образом, должно произойти встраивание нового блока в начало списка.

Буду очень признателен, если поможешь мне понять, что тут не так :)

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

В поле prev следующего блока (на него указывает np->next) записываю np - указатель на текущий блок.

Так таблица же изначально пустая. Отсюда и проблема.

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

А, т.е. фишка банально в том, что следующего блока, куда я планирую писать, нет?) Тогда, наверное, достаточно будет сделать проверку, а есть ли блок. Кстати, а указатели инициализируются NULL'ом же всегда, да?

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

А, т.е. фишка банально в том, что следующего блока, куда я планирую писать, нет?)

Откуда же ему взяться.

Кстати, а указатели инициализируются NULL'ом же всегда, да?

Если объявлены глобально или статически, то да. Иначе только если инициализировать явно.

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

Откуда же ему взяться.

Ну, да, что-то я забыл этот случай рассмотреть.

Если объявлены глобально или статически, то да. Иначе только если инициализировать явно.

Понял, спасибо большое!

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

Я прочитал про битовые поля, но не всё оказалось для меня ясным. Прочитал на википедии и на geektimes немного про машинные слова, ситуация прояснилась, но осталось несколько вопросов. Пожалуйста, если тебе не трудно, помоги мне с ними разобраться.

Как я понимаю, машинное слово - это область в памяти, равная нескольким байтам (количество байт зависит от архитектуры процессора или от чего-то другого?). Я прочитал, что в памяти переменные выравниваются по границе, кратной своему размеру. Какая здесь связь с размером машинного слова и самим машинным словом?

Параграф начинается словами:

Если место в памяти - на вес золота <...> приходится упаковывать несколько объектов в одно машинное слово.

Как один из таких случаев приводится ситуация с однобитовыми флажками, и первый способ для работы с ними - это создать переменную типа char или int, и работать с битами с помощью битовых операций. Получается, что char (1 байт, как я понимаю) можно использовать как 8 однобитовых переменных. Однако, мне не ясно, как это будет выглядеть в контексте машинных слов. Вот у меня есть машинное слово размером, допустим, в 4 байта. Первый байт - char, и я работаю с составляющими его битами. Как это поможет мне уместить несколько объектов в одно машинное слово? Если char занимает 1 байт, то он может принимать 2^8 разных значений; вроде, описанная работа с битами - это только способ трактовки значения в нём.

Кроме того, я прочитал, что

байт является наименьшей единицей, в которой отсчитываются адреса

Как это соотносится с тем, что мы спокойно выполняем операции с отдельными битами?

Как альтернативный вариант предлагается использование битовых полей. Пример:

struct {
    unsigned int is_keyword : 1;
    unsigned int is_extern : 1;
    unsigned int is_static : 1;
} flags;
Сказано, что здесь определяется переменная flags, содержащая три однобитовых поля. По синтаксису, flags - это структура из трёх сущностей, каждая из которых, как я понимаю, обозначает одно битовое поле и занимает один бит. Как это представляется с точки зрения машинных слов и выравнивания переменных? И, если байт - минимальная область, которую может занимать переменная, то даже если тут используются три бита, занят этой структурой будет целый байт, и экономии опять не выходит?

Заранее большое спасибо!

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

количество байт зависит от архитектуры процессора или от чего-то другого?

От этого и от того, как сложится исторически. Значение у «слова» несколько и это либо лучший размер для архитектуры, либо то, что исторически было (на x86 это два байта, так как на 16-битных оно так было, а потом начали четверное слово, восьмерное и подобное писать).

char (1 байт, как я понимаю)

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

мне не ясно, как это будет выглядеть в контексте машинных слов

Если char, то это 8 бит (CHAR_BIT точнее) и будет, для типов больших размеров будет больше бит. Просто не надо с большими типами работать как с char.

Как это соотносится с тем, что мы спокойно выполняем операции с отдельными битами?

Так что адрес битового поля взять нельзя. Т.е. оператор & не может выдать адрес 2.5 байта, только целые. А то, что дальше, это уже работа со значением внутри адресуемого участка памяти (теже >>, <<, |, &, но делается компилятором).

Как это представляется с точки зрения машинных слов и выравнивания переменных?

Обычно поля заполняют байты последовательно, хотя там ещё little/big endian порядок влияет на это. Выравнивание всей структуры будет до int скорее всего по умолчанию.

и экономии опять не выходит?

Будет, так как иначе понадобилось бы минимум 3 байта.

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

Будет, так как иначе понадобилось бы минимум 3 байта.

Это если выравнивание по int? А от чего зависит, по чему будет получаться выравнивание? Прочитал https://habrahabr.ru/post/142662/, у меня сложилось ощущение, что можно 4 char подряд поместить, а сдвиг ещё на три байта - это если int следом идёт, который 4 байта занимает.

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

Т.е. ситуация такова:

Если мне достаточно нескольких битов (и я хочу сэкономить), то я могу либо: взять 8-битовый char (в принципе, любой тип можно так использовать, наверное?) и писать нужные мне данные в его разряды, либо завести такую структуру с битовыми полями и пользоваться ими. В первом случае будет занят 1 байт, во втором случае, вероятно, будет занято целое число байт, достаточное для вмещения того количества бит, которое я напишу в сумме после двоеточий. Размещение остальных переменных в машинном слове зависит от их длины.

Я правильно понимаю суть?

И правильно ли я понимаю, что любая переменная занимает место, равное 1/2^k от длины слова, и выравнивается строго по своей длине, т.е. при длине слова W переменная длиной W/2 будет размещена либо в первой половине W (если там пусто), либо во второй (если в первой есть хоть что-то), и таким образом я не всегда получу выигрыш от битовых полей/использования отдельных битов переменной? Например, если мне нужно, допустим, 7 «флажков», я их умещаю в байт, но следом у меня идёт 4-байтовая переменная и слово целиком занято:

  • флажки
  • пусто
  • пусто
  • пусто
  • 4-байтовая
  • переменная
  • занимает
  • всё это

И ещё: правильно ли я понимаю, что обычная структура (состоящая из переменных) представляется в памяти именно как несколько отдельных переменных (с соответствующим выравниванием), а структура из битовых полей является одной переменной, размер которой зависит от содержимого?

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

в принципе, любой тип можно так использовать, наверное?

Любой из целочисленных.

Я правильно понимаю суть?

Ага.

и таким образом я не всегда получу выигрыш от битовых полей/использования отдельных битов переменной?

Да. Можно размещать переменные отсортировав по размеру от больших к меньшим, при этом зазоров не будет (в них просто не будет необходимости).

правильно ли я понимаю, что обычная структура (состоящая из переменных) представляется в памяти именно как несколько отдельных переменных (с соответствующим выравниванием)

Да.

а структура из битовых полей является одной переменной, размер которой зависит от содержимого?

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

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

Привет! Я тут столкнулся с кодом, который ведёт себя неожиданно для меня. Вот он: https://gist.github.com/anonymous/65f43af9a01e4a955eaaebde0592b6ba

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

Что меня смущает: в строке 8 я сознательно поставил в конце формата просто процент. Я ожидал, что произойдёт следующее: В цикле for дохожу до этого процента, попадаю в switch. Следующий после него символ - символ \0, которым кончается строка fmt. Этот символ ни одним из case'ов не обрабатывается и просто выводится на экран. После этого выполняется p++ и p теперь указывает на следующий элемент после конца строки (в учебнике говорилось: стандарт языка гарантирует, что адресная арифметика с участием первого элемента после конца массива будет работать корректно. А что в этом первом элементе находится? Ещё один \0? Всегда ли так?). Если там \0, то на этом работа программы должна закончиться, иначе - непредсказуемый результат.

Но программа почему-то спокойно выводит в итоге «2 4 8». Запустил в отладчике, поставив брейкпоинты на строках 26 и 27, смотрю на значение *p. Для первых двух пар остановок значения ожидаемые: %, d, %, d. Потом остановка на 26 даёт %, после continue остановка опять на 26 и опять %, после ещё одного continue остановка на 27 и *p = d, потом continue завершает выполнение программы. Я не понимаю, почему так происходит. Не мог бы ты помочь мне разобраться в происходящем? Заранее спасибо.

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

стандарт языка гарантирует, что адресная арифметика с участием первого элемента после конца массива будет работать корректно. А что в этом первом элементе находится? Ещё один \0? Всегда ли так?

Никто не знает. Работать будет только арифметика, разименовывать там нельзя.

Если там \0, то на этом работа программы должна закончиться, иначе - непредсказуемый результат.

Там просто другая строка, скорее всего. В данном случае это «непредсказуемый результат», так как исходная строка уже закончилась.

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

Т.е. предсказуемое поведение заканчивается чётко на \0?

А что с брейкпоинтами?

(gdb) break 26
Breakpoint 1 at 0x40063f: file 7.3.c, line 26.
(gdb) break 27
Breakpoint 2 at 0x40066b: file 7.3.c, line 27.
(gdb) run
Starting program: /home/user/Программы/C/a.out 

Breakpoint 1, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:26
26	        switch(*++p) {
(gdb) continue
Continuing.

Breakpoint 2, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:28
28	                ival = va_arg(ap, int);
(gdb) continue
Continuing.

Breakpoint 1, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:26
26	        switch(*++p) {
(gdb) continue
Continuing.

Breakpoint 2, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:28
28	                ival = va_arg(ap, int);
(gdb) continue
Continuing.

Breakpoint 1, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:26
26	        switch(*++p) {
(gdb) print *p
$1 = 37 '%'
(gdb) continue
Continuing.

Breakpoint 1, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:26
26	        switch(*++p) {
(gdb) print *p
$2 = 37 '%'
(gdb) continue
Continuing.

Breakpoint 2, minprintf (fmt=0x400874 "%d %d %") at 7.3.c:28
28	                ival = va_arg(ap, int);
(gdb) print *p
$3 = 100 'd'
После %, d, %, d, % я ожидал увидеть \0 на Breakpoint 2, а вместо этого я снова на Breakpoint 1 почему-то.

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

Т.е. предсказуемое поведение заканчивается чётко на \0?

Да, дальше же чужая память.

После %, d, %, d, % я ожидал увидеть \0 на Breakpoint 2, а вместо этого я снова на Breakpoint 1 почему-то.

Так если там \0, то он пойдёт на default: и потом продолжит за \0.

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

Так если там \0, то он пойдёт на default: и потом продолжит за \0.

Тьфу. Я, видимо, неправильно распорядился breakpoint'ами. Как я понимаю, breakpoint останавливает программу прямо перед выполнением строки, номер которой указан. Установив breakpoint на строку 27, я думал, что остановлюсь сразу после строки 26 и print *p покажет мне *p после выполнения ++p. Не подскажешь, как следовало для этого поставить breakpoint?

Norong ★★
() автор топика
Последнее исправление: Norong (всего исправлений: 1)
Ответ на: комментарий от Norong

Как я понимаю, breakpoint останавливает программу прямо перед выполнением строки, номер которой указан?

Да.

как следовало для этого поставить breakpoint?

Можно было обойтись одним breakpoint и сделать (p это сокращённая форма print):

p *(p + 1)
# или
p p[1]
print и некоторые другие команды gdb понимают чуть ли не весь синтаксис C (можно и функции вызывать, правда некоторые могут завершить сессию отладки, если что-то сложное сделают).

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

Привет! У меня тут возникло недопонимание со scanf. Есть такой вызов этой функции:

scanf("%lf %lf %*c %c %c", &num1, &num2, &oper, &ending)
num1 и num2 - double, oper и ending - char.

Этот вызов используется в калькуляторе с обратной польской записью, я считываю два числа типа double, потом пропускаю пробел после них с помощью %*c, считываю знак операции, потом считываю следующий символ. В зависимости от того, будет ли он пробелом, концом строки или EOF, будет принято решение о том, что делать дальше. После вызова функции я ввожу с клавиатуры следующее:

5 5 +<ENTER>

и рассчитываю, что в num1 будет 5, в num2 тоже 5, пробел после пятёрок будет пропущен, в oper будет + и в ending будет '\n'. Тем не менее, ввод не прекращается. После ввода ещё двух символов ввод прекращается, в num1 и в num2 стоят пятёрки, а в oper и ending - два последних символа. Я также попробовал вызов

scanf("%lf %lf %lc %c", &num1, &num2, &oper, &ending)
Тогда после ввода

5 5 +<ENTER>

я должен ввести ещё один символ, и в oper находится +, а в ending последний символ.

Во-первых, мне непонятно, почему эти два вызова дают разный результат. Насколько я понял, %*c считает и пропустит пробел, а %lc считает следующий непробельный символ, и вызовы должны бы сработать одинаково - пропустить пробел после второй пятёрки и взять знак плюс.

Во-вторых, не ясно, почему в первом вызове %*c как будто игнорирует пробел после пятёрки и берёт вместо этого знак плюс, а %c в обоих вызовах будто бы игнорирует конец строки после плюса и считывает следующий символ. В учебнике сказано, что при спецификации %c отменяется обычно включённый пропуск пустого пространства, но тут создаётся ощущение, что он работает.

Не мог бы ты прояснить мне происходящее? Заранее спасибо.

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

В учебнике сказано, что при спецификации %c отменяется обычно включённый пропуск пустого пространства, но тут создаётся ощущение, что он работает.

Для этого перед ним не должно быть пробелов. Пробел в форматной строке означает «любое количество пробельных символов» (включая \n). Т.е. строка должна быть такой:

scanf("%lf %lf%*c%c%c", &num1, &num2, &oper, &ending)
Это отменит специальную обработку пробелов после второго double и всё будет работать как ожидалось. Сопоставлять тот пробел явно нет необходимости на самом деле:
scanf("%lf %lf %c%c", &num1, &num2, &oper, &ending)
И пробел между двумя %lf можно убрать, там он подразумевается (чтобы можно было определить конец первого числа).

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

У меня в итоге такой код получился: https://gist.github.com/anonymous/cd6dd831002344960e10d88ae37c25ff

Он не совсем правильно обрабатывает EOF при вводе из командной строки, потому что если ввести

5 8 +<ENTER>

то проходится полный цикл for, и хотя логично отправить теперь EOF, если вычисления завершены, отправка EOF вступает в конфликт с %lf и программа завершается. Мне не пришло в голову никакого способа для решения этой проблемы, но я нашёл ungetc. Думаю, вникать в её использование раньше, чем будет прочитана теория по файловому вводу/выводу, не стоит, так что, эта проблема меня не беспокоит. Тем не менее, почему-то даже если строка просто заканчивается EOF'ом, программа завершается со словами «Некорректный формат входных данных». Я делаю так:

 printf "5 8 +" | ./a.out 
И ожидаю, что в ending попадёт EOF. Тем не менее, отладочная информация, выводимая в строках 43-44, говорит, что произошёл конфликт при считывании EOF по спецификации %c в char, и в ending находится 0 (а EOF, если я правильно помню, -1). Попробовал сделать ending типа short, вспомнив о том, что все приличные символы имеют неотрицательный код. Может, EOF нельзя поместить в char? Оказалось, что можно, и проблема не в этом. Кстати, я прочитал, что char может быть как знаковым, так и беззнаковым. Я правильно понимаю, что в C все целочисленные переменные по-умолчанию знаковые, если не сказано обратное? На этом идеи о том, почему программа не работает, иссякли. Вероятно, это какая-то хитрость спецификации %c, о которой я не в курсе. Пожалуйста, подскажи, что я делаю не так, если тебе не сложно.

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

И ожидаю, что в ending попадёт EOF.

Не попадёт, его нету в потоке. Это значение только сигнализирует о завершении потока, но никак не читается форматированным вводом (scanf его встречает и просто завершается). Из getc() оно возвращается потому, что иначе никак не сказать об ошибке, а scanf() может вернуть количество реально считанных значений.

Я правильно понимаю, что в C все целочисленные переменные по-умолчанию знаковые, если не сказано обратное?

Все кроме char. Он знаковый/беззнаковый в зависимости от архитектуры. Например, на x86 знаковый по умолчанию, на ARM беззнаковый. Есть ещё флаги компилятора -fsigned-char, -funsigned-char.

Если первое число не считалось, можно проверить на feof(stdin) и просто выйти. Если не использовать дополнительных функций, то либо ввод всегда завершать \n, либо не полагаться на наличие \n (но тогда всё равно нужны будут fgets и sscanf). Так что проще первое.

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

Не попадёт, его нету в потоке.

А getchar(), получается, работает по аналогии с getc()?

Если первое число не считалось, можно проверить на feof(stdin) и просто выйти.

Прочитал про feof. Если EOF как таковой в потоке отсутствует, то, получается, дойдя до конца файла, scanf оставляет поток пустым, а feof проверяет, пусто ли там, и тогда проверку feof'ом надо проводить после вызова scanf, про который я подозреваю, что мог встретиться EOF?

Если не использовать дополнительных функций, то либо ввод всегда завершать \n

Т.е. чтобы каждая строка кончалась \n, а последняя - ещё и EOF? А как мне в моём случае поможет \n? Всё равно же надо будет определять, встретился EOF, или нет, чтобы завершить выполнение программы.

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

А getchar(), получается, работает по аналогии с getc()?

getchar() == getc(stdin)

Если EOF как таковой в потоке отсутствует, то, получается, дойдя до конца файла, scanf оставляет поток пустым, а feof проверяет, пусто ли там, и тогда проверку feof'ом надо проводить после вызова scanf, про который я подозреваю, что мог встретиться EOF?

Только проверка в feof() не на пустоту, а на флаг. Если поток дошёл до конца, то флаг взводится и остаётся в таком состоянии (сбрасывается вручную, но это редко надо). А так да.

Т.е. чтобы каждая строка кончалась \n, а последняя - ещё и EOF?

Так практически всегда и есть.

А как мне в моём случае поможет \n? Всё равно же надо будет определять, встретился EOF, или нет, чтобы завершить выполнение программы.

Я думал, последний %c можно заменить на \n, но, видимо, не получится так.

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

Флаг - это бит, который есть у каждого потока, и говорит о том, достигнут ли был конец файла при чтении из потока? А как происходит определение конца потока, если EOF в самом потоке нет? И если из потока действительно изымаются символы при их чтении, то в чём принципиальное отличие проверки флага от проверки «пустоты»?

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

Флаг - это бит, который есть у каждого потока, и говорит о том, достигнут ли был конец файла при чтении из потока?

Да.

А как происходит определение конца потока, если EOF в самом потоке нет?

Когда системный вызов возвращает признак конца данных («считано 0 байт»).

И если из потока действительно изымаются символы при их чтении, то в чём принципиальное отличие проверки флага от проверки «пустоты»?

Поток может быть пустым временно, а флаг обеспечивает фиксирование этого состояния.

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

Понял, спасибо!

И у меня возник опять вопрос по размещению переменных в машинных словах. Ты раньше сказал:

Можно размещать переменные отсортировав по размеру от больших к меньшим, при этом зазоров не будет (в них просто не будет необходимости).

Правильно ли я понимаю, что порядок размещения переменных в памяти зависит от порядка их объявления? Кроме того, ты раньше говорил, что локальные переменные размещаются «на стеке». Это какая-то отдельная область памяти, отличная от той, где выделяется место под глобальные переменные? Если так, то, получается, упорядочивать переменные для экономии памяти имеет смысл раздельно для глобальных и для локальных каждой функции?

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

Не столько FAQ, сколько сборник комментариев к учебнику K&R. Всё-таки, мои вопросы, на которые xaizek любезно отвечает, вдохновлены его чтением. :)

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

Norong молодец, нормально задавать вопросы тоже надо уметь, а то были бы не ответы, а одни уточнения вопросов. Как дополнение к K&R может кому-нибудь и сгодится.

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