цель заголовочных файлов в C только предупреждение для пользователей? - PullRequest
0 голосов
/ 12 ноября 2018

Я новичок в Linking, извините, если мои вопросы слишком просты. допустим, у меня есть два файла .c

file1.c is

int main(int argc, char *argv[]) 
{
    int a = function2();
    return 0;
}

file2.c is

int function2() 
{ 
    return 2018;
}

Я знаю, что норма - создать file2.h и включить его в file1.c, и у меня есть несколько вопросов:

Q1. #include в file1.c не имеет для меня большого значения или улучшения, я все равно могу правильно скомпилировать file1.c без file2.h, компилятор просто предупредит меня ' неявное объявление функции' function2 ', но много ли помогает это предупреждение? Программисты могут знать, что функция2 определена в другом файле .c (если вы используете функцию2, но не определяете ее, вы наверняка знаете, что это определение где-то еще), и компоновщик выполнит свою работу для создания окончательного исполняемого файла? так что единственная цель включения file2, c для меня - не показывать никаких предупреждений во время компиляции, это мое понимание правильно.

Q2. Вообразите этот сценарий, программист определяет функцию 2 в file1.c, он не знает, что его функция 2 конфликтует с функцией в file2.c до тех пор, пока компоновщик не выдаст ошибку (очевидно, он может правильно скомпилировать свой файл file1.c один. Но если мы хотим, чтобы он знал о своей ошибке, когда он компилирует свой file1.c, добавив file2.h все еще не помогает, так какова цель добавления файла заголовка?

Q3. Что мы должны добавить, чтобы программист знал, что он должен выбрать другое имя для function2, а не сообщать об ошибке компоновщику на последнем этапе.

Ответы [ 4 ]

0 голосов
/ 12 ноября 2018

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

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

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

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

у вас очень простой пример, всего 2 файла. Теперь представьте проект с сотнями файлов и тысячами функций. Вы потеряетесь в ошибках компоновщика.

'c' позволяет вам использовать необъявленную функцию по устаревшим причинам. В этой ситуации предполагается, что функция имеет возвращаемый тип int. Однако современные типы данных имеют большую достоверность, чем в первые дни. Функция может возвращать указатели, 64-битные данные, структуры. Чтобы выразить это, вы должны использовать прототипы, иначе ничего не получится. Компилятор должен знать, как правильно обрабатывать возвращаемые функции.

Кроме того, он может предупреждать о неправильном использовании типов аргументов. Из-за слабости это все еще предупреждения, но они были исправлены в раннем c ++ и преобразованы в ошибки.

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

Итак, в вашем примере вам не нужен заголовочный файл. Вы можете создать прототип функции в «основном» файле, используя синтаксис «extern». Вы даже можете обойтись без прототипирования. Однако в реальном современном мире программирования вы не можете допустить последнего. В частности, когда вы работаете в команде или хотите, чтобы ваша программа была ремонтопригодной.

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

0 голосов
/ 12 ноября 2018

За C89 3.3.2.2 Вызовы функций выделение мин:

Если выражение, которое предшествует заключенному в скобки списку аргументов в вызове функции, состоит исключительно из идентификатора, и , если для этого идентификатора не видно никакого объявления , идентификатор неявно объявлен точно как будто в самом внутреннем блоке, содержащем вызов функции, объявление

    extern int  identifier();

появился

Теперь, помните, пустой список параметров (объявленный с пустыми скобками ()) объявляет функцию, которая принимает неопределенный тип и количество аргументов. Введите void внутри фигурных скобок, чтобы объявить, что функция не принимает аргументов, например int func(void).

Q1:

много ли помогает это предупреждение?

Да и нет. Это субъективный вопрос. Помогает тем, кто его использует. Как личное примечание, всегда делайте это предупреждение ошибкой. При использовании компилятора gcc используйте -Werror=implicit-function-declaration. Но вы также можете проигнорировать это предупреждение и сделать простейшую main() { printf("hello world!\n"); } программу.

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

Нет. В случаях, когда функция вызывается с использованием другого / несовместимого типа указателя. Это вызывает неопределенное поведение. Если функция объявлена ​​как void (*function2(void))(int a);, то вызов ((int(*)())function2)() является UB, как и вызов function2() без предварительного объявления. Согласно Приложению J.2 (информативное):

Поведение не определено в следующих обстоятельствах:

Указатель используется для вызова функции, тип которой не совместим с указанным типом (6.3.2.3).

и за C11 6.3.2.3p8 :

Указатель на функцию одного типа может быть преобразован в указатель на функцию другого типа и обратно; результат должен сравниваться равным исходному указателю. Если преобразованный указатель используется для вызова функции, тип которой не совместим с указанным типом, поведение не определено.

Так что в вашем счастливом случае int function2() действительно это работает. Это также работает, например, для функции atoi(). Но вызов atol() вызовет неопределенное поведение.

2:

компоновщик выдает ошибку

Это должно произойти, но в действительности зависит от линкера. Если вы скомпилируете все исходники, используя один этап с помощью компилятора gcc, это выдаст ошибку. Но если вы создаете статические библиотеки, а затем связываете их с помощью компилятора gcc без -Wl,-whole-archive, тогда он выберет первое объявление, смотрите this thread .

какова цель добавления заголовочного файла?

Наверное, простота и порядок. Это удобный и стандартный способ обмена структурами данных (enum, struct, typedefs) и объявлениями (типами функций и переменных) между разработчиками и библиотеками. Также поделиться директивами препроцессора. Представьте, что вы пишете большую библиотеку с более чем 1000+ файлами, которые будут работать с более чем 100+ другими библиотеками. В начале каждого файла вы бы написали struct mydata_s { int member1; int member2; ... }; int printf(const char*, ...); int scanf(const char *, ...); etc. или просто #include "mydata.h" и #include <stdio.h>? Если вам нужно изменить структуру mydata_s, вам нужно будет изменить все файлы в вашем проекте, и всем другим разработчикам, которые используют вашу библиотеку, потребуется изменить и это определение. Я не говорю, что вы не можете сделать это, но было бы больше работы, и никто не будет использовать вашу библиотеку.

Q3:

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

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

0 голосов
/ 12 ноября 2018

Краткий ответ:

Уберите: чем раньше предупреждение компилятора, тем лучше.

Q1: значение .h: согласованность и ранние оповещения. Раннее оповещение о распространенных способах неправильной работы повышает надежность кода и сводит к минимуму отладку и производственные сбои.

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

В3. Ранние предупреждения о дублировании не включаются в стандарт C.

Упражнения: 1. Определите функцию в одном файле, которая printf ("% d \ n", i) аргумент int, затем вызовите эту функцию в другом файле с плавающей точкой 42.0. 2. Позвоните с (двойной) 42,0. 3. Определите функцию с аргументом char * str, напечатанным в% .s, затем вызовите с аргументом int.

Более длинные ответы:

Популярное соглашение: при типичном использовании имя файла .h происходит от файла .c или файлов, с которыми оно связано. file.h и file.c. Для файлов .h со многими определениями, скажем, string.h, выведите имя файла с точки зрения того, что находится внутри (как в функциях str ...).

Мое большое правило: всегда лучше структурировать ваш код, чтобы компиляторы могли сразу же предупреждать об ошибках во время компиляции, а не позволять им скользить в отладке или во время выполнения, когда они зависят от кода, который выполняется на самом деле, как раз для правильного поиска. Ошибки во время выполнения могут быть очень трудно диагностировать, особенно если они появляются очень долго после того, как программа запущена в производство, и дороги в обслуживании и снижают качество обслуживания клиентов. Смотрите "обозначение йода".

Q1: значение .h: согласованность и ранние оповещения и повышение надежности кода.

C .h файлы позволяют разработчикам .c файлов, скомпилированных в разное время, обмениваться общими объявлениями. Нет повторяющегося кода. Файлы .h также позволяют последовательно вызывать функции из всех файлов, одновременно выявляя неправильные подписи аргументов (количество аргументов, плохие конфликты и т. д.). Наличие .c файлов, определяющих функции, также #include .h файл помогает гарантировать, что аргументы в определении согласуются с вызовами; это может показаться элементарным, но без этого могут проникнуть все человеческие ошибки в сигнатурных столкновениях.

Пропуск файлов .h работает только в том случае, если подписи аргументов всех вызывающих абонентов полностью совпадают с сигнатурами в определениях. Часто это не так, поэтому без файлов .h любые конфликтующие подписи будут давать неверные числа, если у вас также не будет параллельных внешних символов в вызывающем файле (плохие плохие плохие). Такие вещи, как int vs float, могут давать совершенно неверные значения аргументов. Плохие указатели могут привести к ошибкам сегмента и другим общим сбоям.

Преимущество: с помощью externs в .h файлах компиляторы могут корректно приводить несоответствующие аргументы к правильному типу, обеспечивая лучшие вызовы. Хотя вы все еще можете опровергнуть аргументы, это гораздо менее вероятно. Это также помогает избежать условий, когда несоответствия работают в одной реализации, но не в другой.

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

Q2: столкновение имен. Ранние оповещения.

Конфликтующие имена - это плохо, и разработчики обязаны избегать проблем. C ++ решает проблему с пространствами имен, которых нет в C, как языке более низкого уровня.

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

Распространенным способом подделки пространств имен является запуск всех потенциально конфликтующих определений в .h с некоторым префиксом (extern int filex_function1 (int arg, char * string) или #define FILEX_DEF 42).

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

Q3: ранние дубликаты предупреждений. Извините ... ранние оповещения зависят от реализации.

Этобыло бы трудно определить стандарт Си. Поскольку C является старым языком, существует множество различных способов написания и хранения программ на C. Охота за конфликтами имен перед их использованием остается за разработчиком. Инструменты, такие как программы перекрестных ссылок, могут помочь. Даже такие глупости, как ctags, связанные с vim или emacs, могут помочь.

0 голосов
/ 12 ноября 2018

Q1.Да.C является языком низкого уровня и исторически использовался для связывания низкоуровневых конструкций в понятия более высокого уровня.Например, традиционно метка _end находится по последнему адресу в программе.Ярлык не имеет типа, но вы можете объявить его любым удобным для вас типом.«Правильно типизированный» язык усложнит такое злоупотребление.

Q2.По соглашению, и file1.c, и file2.c должны включать file2.h;один как потребитель, другой как производитель.Следуя этой простой идиоме, вы поймете ошибки объявления и определения;хотя, опять же, «предупреждение» не обязательно исполняется.

Q3.Многие организации, занимающиеся разработкой программного обеспечения, применяют правило «предупреждения - ошибки», чтобы контролировать своих программистов.

...