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,
Не могу понять, что не так. Пожалуйста, помогите разобраться.

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

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

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

Только по вызовам (они дают неразрешённые символы в o-файлах), объявления следов в объектных файлов не оставляют.

А компилятор работает только с обособленными единицами трансляции, которые получаются после подстановки include и разворачивания макросов (если считать препроцессор отдельным элементом, который это и делает).

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

Большое спасибо, картина сложилась!

Что бы я делал без твоей помощи..? Мне даже неловко. Тебя это не очень затрудняет?

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

Та не, я вон тоже заодно освежаю некоторые спорные моменты в памяти, подглядывая в стандарт в процессе.

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

У меня возникли ещё вопросы о внешних переменных:

Внешняя переменная должна быть определена ровно один раз за пределами всех функций программы. ... Она также должна быть объявлена в каждой функции, которая к ней обращается, с указанием её типа. Объявление может быть явным, с помощью оператора extern, а может быть и неявным - по контексту.

Какая в контексте этого абзаца разница между определением переменной и её объявлением?

Дальше:

ровно один раз за пределами всех функций программы.

А если у меня несколько файлов исходного кода, и я хочу, чтобы внешняя переменная из одного файла использовалась в других, то я должен как минимум в одном файле исходного кода (любом) объявить переменную без extern вне функций, а в остальных - с extern или без вне функций, и/или с extern внутри тех функций, в которых я хочу её использовать? А могу ли я такую переменную объявить внутри функции без extern, и будет ли разница?

Ты говорил:

Хотя бы одно объявление во множестве файлов должно быть без extern иначе переменная не будет создана нигде.

Правильно ли я понимаю, что без extern происходит либо отведение памяти под переменную, либо обращение к уже существующей, а с extern - только обращение к уже существующей? Если да, то что из этого делает компилятор, а что - компоновщик?

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

Какая в контексте этого абзаца разница между определением переменной и её объявлением?

Определение чаще всего то, которое с инициализацией. Т.е. так делать нельзя:

int a = 1;
int a = 2;
Но так как определение может быть и без инициализации, то каждое объявление трактуется двояко: может быть определение с инициализацией нулём, а может и только лишь объявление. Я, пользуясь этой неоднозначность, сделал авторегистрацию тестов на C (тут что-то расписано, правда там текста много, а в комментариях люди пишут, что всё равно не поняли как оно работает). Так вот, всё неоднозначное объявляется в объектном файле как common symbol (C) (в man nm есть описания). А при инициализации переменная определяется как нормальная инициализированная переменная (D). Линкер потом это совмещает и если есть только C, то оно и остаётся, но если где-то есть D, то берётся этот символ. Если есть два D, то будет ошибка из-за двойного определения.

А если у меня несколько файлов исходного кода, и я хочу, чтобы внешняя переменная из одного файла использовалась в других, то я должен как минимум в одном файле исходного кода (любом) объявить переменную без extern вне функций, а в остальных - с extern или без вне функций, и/или с extern внутри тех функций, в которых я хочу её использовать? А могу ли я такую переменную объявить внутри функции без extern, и будет ли разница?

Да, только без «extern» в функции это будет обычная локальная переменная.

Правильно ли я понимаю, что без extern происходит либо отведение памяти под переменную, либо обращение к уже существующей, а с extern - только обращение к уже существующей? Если да, то что из этого делает компилятор, а что - компоновщик?

Правильно. Как выше написано компилятор пытается угадать, а линкер имея все объектные файлы принимает решение, что попадёт в исполняемый файл.

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

Но так как определение может быть и без инициализации

Под определением без инициализации подразумевается объявление (тип название; как я понимаю)?

каждое объявление трактуется двояко: может быть определение с инициализацией нулём, а может и только лишь объявление.
Так вот, всё неоднозначное объявляется в объектном файле как common symbol (C)

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

Как выше написано компилятор пытается угадать

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

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

Под определением без инициализации подразумевается объявление (тип название; как я понимаю)?

Да, объявление без extern.

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

Нет, глобальные заполняются нулями, если инициализатор отсутствует. Они не инициализированны пользователем, поэтому об этом заботится язык/система.

а где выше про компилятор?

Там где про символы C и D типов, их же компилятор создаёт на основе кода.

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

Что-то у меня в голове плохо совмещаются вот эти данные:

каждое объявление трактуется двояко: может быть определение с инициализацией нулём, а может и только лишь объявление
Нет, глобальные заполняются нулями, если инициализатор отсутствует.
Так вот, всё неоднозначное объявляется в объектном файле как common symbol (C)

Получается, что компилятор уже знает, будет ли объявление заполнять переменную нулями, либо же только объявит. Тогда для компилятора в этом не должно быть неоднозначности, вроде (и для пишущего программу тоже).

Или там логика такая: не указано, что именно будет в переменной при её объявлении -> неоднозначность -> в объектном файле обозначаем как «C», и фишка именно в том, что в исходнике ничто про значение не указано?

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

может быть определение с инициализацией нулём
глобальные заполняются нулями, если инициализатор отсутствует
всё неоднозначное объявляется в объектном файле как common symbol (C)

Вот это всё одно и то же и происходит в этом случае:

int i;
double d;
void *bla;
<type> name;

Получается, что компилятор уже знает, будет ли объявление заполнять переменную нулями, либо же только объявит. Тогда для компилятора в этом не должно быть неоднозначности, вроде (и для пишущего программу тоже).

В том то и дело, что нет, поэтому делает допущение, что это определение, полагаясь на линкер. Линкер потом разберётся на основании того, будет ли где-то объявление с явной инициализацией или нет. У определения с инициализацией приоритет выше, поэтому компилятор может безопасно предполагать, что видит определение.

Или там логика такая: не указано, что именно будет в переменной при её объявлении -> неоднозначность -> в объектном файле обозначаем как «C», и фишка именно в том, что в исходнике ничто про значение не указано?

Да, вроде такого.

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

Спасибо! Я почти понял. Завтра еще раз всё перечитаю на свежую голову и, наверное, окончательно пойму :)

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

Я перечитал все твои сообщения, но всё-таки осталось несколько вопросов.

В контексте C и D в объектном файле: я правильно понимаю, что переменные, объявленные с extern, как-то иначе там отражаются, ведь, как я понял, и C (неинициализированная) и D (инициализированная) после «объединения» приводят к выделению памяти, а extern, вроде, не приводит?

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

Т.е. он предполагает определение без инициализации?

А что вообще следует из этих предположений компилятора? Как раз-таки C или D в объектных файлах?

И почему всё-таки он вынужден предполагать и не может определить точно? Есть инициализация - D, иначе - C, компилятор же видит, есть ли равно после имени переменной, или нет.

И наконец:

каждое объявление трактуется двояко
всё неоднозначное объявляется в объектном файле как common symbol (C)

Я увидел какую-то такую логику: при отсутствии инициализации отсутствует, так скажем, волеизъявление программиста, и поэтому надо обозначить как C, а при наличии инициализации волеизъявление присутствует, поэтому D. Но какое значение здесь имеет тот факт, что объявление без инициализации может привести к двум разным результатам - нули или мусор?

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

В контексте C и D в объектном файле: я правильно понимаю, что переменные, объявленные с extern, как-то иначе там отражаются, ведь, как я понял, и C (неинициализированная) и D (инициализированная) после «объединения» приводят к выделению памяти, а extern, вроде, не приводит?

Да, оно там undefined (U).

Т.е. он предполагает определение без инициализации?

Да.

А что вообще следует из этих предположений компилятора? Как раз-таки C или D в объектных файлах?

C. Если бы он записал D, то линкер выдал бы ошибку из-за переопределения символа.

И почему всё-таки он вынужден предполагать и не может определить точно? Есть инициализация - D, иначе - C, компилятор же видит, есть ли равно после имени переменной, или нет.

Потому что с точки зрения языка, объявление без инициализации может быть как объявлением, так и определением. На основе одного файла компилятор не может быть уверен в том, что это. Если будет везде писать D, линкер выдаст ошибку. Если везде U, выдаст другую (нету определения). Поэтому есть C, которое предоставляет инициализацию нулём, если нигде нет другой инициализации. А несколько C объединяются в одну область памяти, поэтому не возникает конфликтов.

Но какое значение здесь имеет тот факт, что объявление без инициализации может привести к двум разным результатам - нули или мусор?

Глобальные объявления не могут содержать мусор, там будут нули, если ничего другого не задано. Символ типа C является частью сегмента памяти, который всегда забивается нулями. D содержит явную инициализацию. Если нет ни того ни другого — ошибка. Если есть D — используется он. В противном случае используется C, а там нули.

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

Дошло! Ура :) Спасибо!

Я правильно понимаю, что всё вышесказанное (включая вчерашнее) - только для внешних переменных, а для локальных (внутренних для функций) всё обстоит иначе? Могу предположить, что в их случае компилятор сразу знает, есть ли инициализация или нет. В случае, если есть - D, иначе - b, наверное? (из прочтения man nm)

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

Я правильно понимаю, что всё вышесказанное (включая вчерашнее) - только для внешних переменных

Да.

а для локальных (внутренних для функций) всё обстоит иначе? Могу предположить, что в их случае компилятор сразу знает, есть ли инициализация или нет. В случае, если есть - D, иначе - b, наверное? (из прочтения man nm)

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

b тоже заполняются нулями, это как C, но без дополнительных условий на разрешение конфликтов.

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

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

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

Никакого хорошего объяснения, чтобы сослаться, не помню. Компоновщик о локальных вообще не знает, они для него не существуют.

Получается, что стек - это что-то типа временной области?

Да. Можно сказать массив с указателем на текущую вершину, при входе в фукцию вершина меняется на количество байт, необходимое под локальные переменные и эта часть памяти (от старого положения указателя до нового) используется в функции, при выходе значения указателя возвращается в прежнее состояние и переменные как бы перестают существовать. Это происходит во время исполнения, поэтому компоновщик тут не при чём.

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

Мне тут в учебнике говорят, что

если же необходимо обратиться к внешней переменной до её определения или если она определена в другом файле исходного кода, то обязательно нужно вставить объявление с ключевым словом extern

Это - устаревшие сведения? У меня, вроде, внешние между несколькими файлами переменные отлично себя без extern чувствовали.

Кроме того, в учебнике говорят:

размеры массивов обязательно указываются в определении, но необязательно - в объявлении со словом extern.

Проверил. Если массив, определённый в одном файле, объявить в другом без extern, то требуется указание размера. С extern - не требуется. У меня складывается ощущение, что лучше использовать extern везде, где имеется в виду объявление внешней переменной (кроме как, может быть, при использовании внешней переменной внутри функции, если вне функции она была объявлена в том же файле). Это будет правильно?

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

Это - устаревшие сведения? У меня, вроде, внешние между несколькими файлами переменные отлично себя без extern чувствовали.

Не помню таких требований, скорее просто осторожничают, чтобы случайно не определить что-то не там где нужно (в случае опечатки, например).

У меня складывается ощущение, что лучше использовать extern везде, где имеется в виду объявление внешней переменной (кроме как, может быть, при использовании внешней переменной внутри функции, если вне функции она была объявлена в том же файле). Это будет правильно?

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

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

А при использовании внешних переменных внутри функции необходимо как минимум одно: либо объявлена вне функции выше по тексту, либо объявлена с extern внутри функции, так?

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

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

Мне почему-то приятнее писать код в соответствии с нюансами такого рода. И чтобы без warning'ов. :)

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

У меня возник вопрос про статические внутренние (локальные) переменные. Я понял так: Когда я объявляю такую переменную внутри функции, под неё выделяется память, и вместо того, чтобы содержать мусор, эта переменная инициализируется нулями. А если я её хочу проинициализировать чем-то другим, то я её внутри функции определяю вот так:

static int buf = 4;

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

Я правильно всё это понимаю, или есть какие-то хитрости?

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

На самом деле это как глобальная переменная, только видна исключительно в одной функции. Создаётся и инициализируется во время компиляции.

А локальные это те, которые на стеке. Статические локальными обычно не называют.

Инициализация при первом входе это в C++, но память там тоже выделяется при компиляции. Разница в том, что в C инициализотором может быть только константа, поэтому всё можно сделать во время компиляции.

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

Т.е. отличия от глобальной (внешней) переменной два:

1.) Видна только в одной функции.

2.) Создаётся и инициализируется во время компиляции. Как следствие: инициализация единожды и только константами.

Ага?

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

1.) Видна только в одной функции.

Да.

2.) Создаётся и инициализируется во время компиляции. Как следствие: инициализация единожды и только константами.

Это не отличие, глобальные работают аналогично. Просто у них сложности с объявлениями/определениями в разных местах.

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

Это не отличие, глобальные работают аналогично. Просто у них сложности с объявлениями/определениями в разных местах.

А можно поподробнее про это? Глобальные тоже только константами инициализируются? И в чём там сложности? (или это о том, что ты мне объяснял вчера/позавчера?)

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

А можно поподробнее про это? Глобальные тоже только константами инициализируются?

Да, причём именно константными выражениями. Вот так нельзя:

const int a = 1243;
const int b = a;
Это сделано как раз чтобы компилятору было проще, иначе надо откладывать инициализацию до момента запуска программы.

И в чём там сложности? (или это о том, что ты мне объяснял вчера/позавчера?)

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

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

Мне тут в задании предложили написать рекурсивную версию функции, которая преобразует целое число в строку. Я написал её, вот она: https://gist.github.com/anonymous/3fa0420c719eed30f8bd3c083da63b64

В первой версии у меня получилась функция, настолько тесно завязанная на остальную программу (i и string были внешними переменными, itoa вызывалась с одним аргументом - числом, а string был захардкожен), что самодостаточной её нельзя было назвать. Потом я решил, что i должен быть статической переменной внутри функции itoa, а string - передаваться в itoa аргументом, причём таким, чтобы при вызове itoa рекурсивно из той же itoa этот аргумент оставался тем же (т.е. все вызовы itoa работали с одним и тем же массивом). И я сделал вот такое:

void itoa(int n, static char s[]) {

Компилятор мне этого не простил.

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

У меня возникло несколько вопросов:

  • Правильно ли я вспомнил эту информацию о массиве в роли аргумента?
  • Если да, то как так получается? У меня же в этом случае массив может не быть внешней переменной. Что там происходит при таком вызове?
  • Если я захочу, чтобы функция, вызванная с неким аргументом, не являющимся массивом, работала непосредственно с ним, не создавая копию, я должен прежде сделать ту переменную, которую передаю в функцию, внешней? Потребуются ли ещё какие-нибудь манипуляции, кроме этой? В функцию я её передаю как обычно, наверное. А в заголовке функции не должно быть чего-то типа extern i?
Norong ★★
() автор топика
Ответ на: комментарий от Norong

Правильно ли я вспомнил эту информацию о массиве в роли аргумента?

Правильно.

Если да, то как так получается? У меня же в этом случае массив может не быть внешней переменной. Что там происходит при таком вызове?

Независимо от типа переменной (хотя, если только она не временная, которая порождается вычислениями), у неё есть адрес места, где переменная лежит в памяти. Массив «разлагается» до адреса и копируется уже он, а при операциях компилятор работает с тем, что лежит по этому адресу (косвенное обращение).

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

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

А в заголовке функции не должно быть чего-то типа extern i?

Нет, там такого не будет, но будет другая нотация.

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

Спасибо, понял! Предвкушаю пятую главу...

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

И вот я на пятой главе. Там есть пример функции, которая делает следующее: Русский вариант:

Выполняет преобразование неформатированного входного потока, разбивая цепочку символов на целые числа и вводя по одному числу за один вызов. Функция должна возвращать обнаруженное ею число, а также сигнализировать о конце файла, если больше нет входных данных. Наша версия getint возвращает EOF, если встретился конец потока, нуль, если следующий элемент данных не является числом, положительное число, если в потоке встретилось нормальное целое число.

Английский вариант:

Performs free-format input conversion by breaking a stream of characters into integer values, one integer per call. getint has to return the value it found and also signal end of file when there is no more input. Our version of getint returns EOF for end of file, zero if the next input is not a number, and positive value, if the input contains a valid number.

Кроме того, упражнение к этому коду гласит:

Функция getint в её нынешнем виде воспринимает плюс или минус без последующей цифры как разрешённое представление нуля. Измените функцию так, чтобы она возвращала символ назад в поток.

Код я перепечатал из учебника, проверил пару раз:

wget http://ix.io/17dj -O - | base64 -d | tar -x

Что этот код в моём представлении должен делать (это из моего понимания задания): до получения EOF считывать символы, и каждый раз, когда встречается последовательность, являющаяся числом, записывать её, как число, в следующий элемент массива.

Однако, вот что происходит не так, как мне кажется правильным:

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

Меня смутила строка 14 в getint.c - то есть, если символ - не цифра, не EOF и не знак, просто вернуть его обратно в буфер и вызвать функцию ещё раз? Буфер - внешний, тогда я при следующем getch() опять получу тот же символ и результат будет немного предсказуем. В массив при таком сценарии ничего не запишется, но n увеличивается при каждом вызове функции (если не вернёт EOF), т.е. получается, что вся программа завершится при получении первого символа, не являющегося цифрой, ± или EOF.

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

wget http://ix.io/17em -O - | base64 -d | tar -x

Собственно, единственный вопрос, который меня терзает: что это было? Либо я неправильно понял задание, либо в учебнике просто неправильный код. Если тебе не трудно, не мог бы ты высказать своё мнение по этому поводу?

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

А, и ещё: в их версии и в моей версии в функции main объявляется функция getint. С одной стороны, мне это кажется абсолютно нормальным, с другой стороны, раньше всегда объявление функций происходило вне каких-либо функций. Тут всё в порядке, нет нюансов?

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

Сама функция там выглядит выполняющей то, что от ней хотели. Даже для ввода +/- она вернёт есть или нет дальше что-то, и число ноль тоже ввела. Там просто в main() не хватает обработки ошибок, которые эта функция возвращает. При нуле предполагается либо завершение чтения строки, либо вычитывание не-чисел и повторного вызова функции.

Убрав ungetch() (их версия на много символов, но больше одного не используется, поэтому стандартное ungetc() тоже сойдёт), ты также убрал возможность работы с числами в строке, в которой есть не только числа. А в задании же «Измените функцию так, чтобы она возвращала символ назад в поток.».

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

Я убрал эти две функции, заменив их двумя переменными и работой с ними прямо в getint. Буфер по-прежнему есть, все работает хорошо.

Все понял, спасибо!

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

Хотя, нет, не уверен, что всё понял.

При нуле предполагается либо завершение чтения строки, либо вычитывание не-чисел и повторного вызова функции.

Я правильно понимаю, что имеется в виду, что main должен, исходя из возвращённого getint значения, при том, что getint вернул в буфер неправильный символ, сам решить, что делать с возвращённым в буфер символом?

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

Ну да. Может надо вычитать строку:

id name year
Если буфер внутри функции, то первый символ имени будет потерян. Поэтому буфер в стандартной библиотеке, из которой символы можно читать на любом уровне.

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

И ещё небольшой вопрос. Вот есть немного теории: https://i.imgur.com/SRw7oqu.png

Я правильно понимаю, что если я в функцию передаю имя массива, то я могу в функции написать, что она принимает как аргумент массив (char s[]), и тогда внутри функции я работаю с тем же самым массивом, а могу написать, что она принимает как аргумент указатель (char *s), и тогда работаю в терминах указателей?

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

Записи char s[] и char *s в списке параметров ничем друг от друга не отличаются кроме синтаксиса. В обоих случаях это указатель.

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

И снова я.

У меня тут есть небольшой код:

#include <stdio.h>

void strcat(char *s, char *t);

int main(void) {
    char string[20] = "pelmeni";
    char append[] = "append";

    printf("%s\n%s\n", string, append);
    strcat(string, append);
    printf("%s\n", string);

    return 0;
}

void strcat(char *s, char *t) {
    while (*s)
        ++s;
    
    while (*s++ = *t++)
        ;
}
При компиляции я столкнулся с
5.3.c:3:6: warning: conflicting types for built-in function ‘strcat’
 void strcat(char *s, char *t);
      ^
Видимо, это из-за того, что функция strcat существует в стандартных библиотеках (грепнул в /usr/include, нашёл эту функцию в string.h). Как я понимаю, ошибки не возникает, так как библиотечная функция у меня не объявлена, т.е. мне просто говорят, что такое имя лучше не использовать. Я попробовал добавить #include <string.h> в начало кода и действительно получил ошибку. После этого я попробовал добавить #undef strcat сразу после #include <string.h>, рассчитывая на то, что библиотечная функция разобъявится обратно и моя сможет полноценно работать, но ошибка никуда не делась. Пожалуйста, подскажи - я неправильно понял суть #undef, или что-то другое не так?

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

#undef влияет только на макросы препроцессора, т.е. на то, что объявлено с помощью #define. Объявление сущностей вроде функций и любых типов им нельзя убирать. #undef уже не существует к тому моменту, когда компилятор обрабатывает типы, препроцессор отрабатывает один раз при старте, а дальше всё происходит без него.

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

Здравствуй! У меня возник теоретический вопрос по многомерным массивам. Говорилось, что при передаче многомерного массива в функцию в качестве аргумента, в объявлении параметров функции нет необходимости указывать длину первого его измерения, потому что имя такого массива является указателем на массив, каждый элемент которого является другим массивом, но при этом обязательно надо указывать длины всех остальных измерений. Мне такое объяснение оказалось совсем непонятным. Не мог бы ты объяснить мне эти моменты?

  • Почему нет необходимости указывать длину первого измерения, но необходимо указывать длины остальных?
  • Это относится только к объявлению параметров при определении функции, или к чему-то ещё (например, к прототипам)?
  • Как многомерный массив представляется в памяти?

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

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