Получение "конфликтующих типов для функции" в C, почему?


Я использую следующий код:

char dest[5];
char src[5] = "test";

printf("String: %sn", do_something(dest, src));

char *do_something(char *dest, const char *src)
{
    return dest;
}

реализация do_something - это не важно. Когда я пытаюсь скомпилировать выше я получаю эти два исключения:

ошибка: конфликтующие типы для ' do_something '(при вызове printf)
ошибка: предыдущее неявное объявление "do_something" было здесь (в строке прототипа)

почему?

10 57

10 ответов:

вы пытаетесь вызвать do_something, прежде чем объявить его. Вам нужно добавить прототип функции перед вашей строкой printf:

char* do_something(char*, const char*);

или вам нужно переместить определение функции выше строки printf. Вы не можете использовать функцию до ее объявления.

в "классическом" языке C (C89 / 90) при вызове необъявленной функции C предполагает, что она возвращает int а также пытается вывести типы его параметров из типов фактических аргументов (нет, он не предполагает, что у него нет параметров, как кто-то предлагал раньше).

в вашем конкретном примере компилятор будет смотреть на do_something(dest, src) вызовите и неявно выведите объявление для do_something. Последний будет выглядеть следующим образом

int do_something(char *, char *)
, далее в коде вы явно объявляете do_something как
char *do_something(char *, const char *)

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

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

у вас есть два варианта: (1) определить его, прежде чем использовать его, или (2) использовать упреждающее объявление без реализации. Например:

char *do_something(char *dest, const char *src);

обратите внимание на точку с запятой в конце.

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

нужно что-то вроде

char *do_something(char *, const char *);

перед printf.

пример:

#include <stdio.h>
char *do_something(char *, const char *);
char dest[5];
char src[5] = "test";
int main ()
{
printf("String: %s\n", do_something(dest, src));
 return 0;
}

char *do_something(char *dest, const char *src)
{
return dest;
}

В качестве альтернативы, вы можете поставить весь do_something функция перед printf.

C Заповедь #3:

K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence. 

http://www.ee.ryerson.ca:8080 / ~elf / hack / God. vs. K+R.html

Смотрите еще:

char dest[5];
char src[5] = "test";

printf("String: %s\n", do_something(dest, src));

внимание на эту строку:

printf("String: %s\n", do_something(dest, src));

вы можете ясно видеть, что выполнить_действие функция не объявлена!

если вы посмотрите немного дальше,

printf("String: %s\n", do_something(dest, src));

char *do_something(char *dest, const char *src)
{
return dest;
}

вы увидите, что вы объявляете функцию после вы используете его.

вам нужно будет изменить эту часть с помощью этого кода:

char *do_something(char *dest, const char *src)
{
return dest;
}

printf("String: %s\n", do_something(dest, src));

Ура ;)

Функция C-Объявление Backgrounder

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

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

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

Урок Истории

в первые дни C, тот факт, что компилятор должен был угадать типы, На самом деле не был проблемой: все типы были более или менее одинаковыми-почти все было либо int, либо указателем, и они были одинакового размера. (На самом деле, в языке B, который предшествовал C, вообще не было типов; все было просто int или указатель, и его тип определялся исключительно тем, как вы его использовали!) Таким образом, компилятор может безопасно угадать поведение любой функции только на основе количества параметров, которые были переданы: если вы передав два параметра, компилятор будет толкать две вещи в стек вызовов, и, по-видимому, вызываемый объект будет иметь два объявленных аргумента, и все это будет выстраиваться. Если вы передали только один параметр, но функция ожидала два, он все равно будет работать, а второй аргумент будет просто игнорироваться/мусор. Если вы передали три параметра, а функция ожидала два, она также будет по-прежнему работать, а третий параметр будет игнорироваться и топтаться локальной функцией переменная. (Некоторые старые c-коды все еще ожидают, что эти правила несоответствующих аргументов тоже будут работать.)

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

перенесемся в сегодня, и мы не совсем в одной лодке. C вырос, и многие люди программируют в нем, которые не являются волшебниками, и чтобы разместить их (и разместить всех остальных, кто регулярно использовал lint в любом случае), компиляторы взяли на себя многие из способностей, которые ранее были частью lint - особенно та часть, где они проверяют ваш код, чтобы убедиться, что он типобезопасен. Ранние компиляторы C позволят вам писать int foo = "hello"; и это было бы просто беспечно назначить указатель на целое число, и это было до вас, чтобы убедиться, что вы не делали ничего глупого. Современные компиляторы C громко жалуются, когда вы ошибаетесь в своих типах, и это хорошо.

Конфликты Типа

Итак, какое все это имеет отношение к таинственной ошибке конфликтного типа в строке объявления функции? Как я уже сказал выше, компиляторы C все еще должны либо знаю или Угадай что означает имя в первый раз, когда они видят это имя при сканировании вперед по файлу: они могут знаю что это значит, если это фактическое объявление функции (или функция "прототип", подробнее об этом вкратце), но если это просто вызов функции, они должны Угадай. И, к сожалению, догадка часто ошибочна.

когда компилятор увидел ваш вызов do_something(), он посмотрел на то, как он был вызван, и он пришел к выводу, что do_something() в конечном итоге будет объявлен такой:

int do_something(char arg1[], char arg2[])
{
    ...
}

почему он пришел к такому выводу? Потому что именно так ты под названием это! (Некоторые компиляторы C могут заключить, что это было int do_something(int arg1, int arg2), или просто int do_something(...), оба из которых являются даже дальше от того, что вы хотите, но важным моментом является то, что независимо от того, как компилятор угадывает типы, он угадывает их иначе, чем то, что использует ваша фактическая функция.)

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

Вам может быть интересно, " почему он предполагает, что я возвращаю int?"Ну, это предполагает, что тип, потому что нет никакой информации наоборот: printf() можно взять в любой введите его переменные аргументы, так что без лучшего ответа,int это так же хорошо догадаться, как и любой. (Многие ранние компиляторы C всегда предполагали int для каждого неопределенного типа, и предположил, что вы имели в виду ... для аргументов для каждой объявленной функции f() - не void - именно поэтому многие современные стандарты кода рекомендуют всегда ставить void в для аргументов, если есть на самом деле их вообще не должно быть.)

Исправления

существует два распространенных исправления для ошибки объявления функции.

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

char *do_something(char *dest, const char *src);

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

другое решение, которое также отображается в некотором реальном коде, заключается в том, чтобы просто переупорядочить ваши функции так, чтобы объявления функций всегда до все, что называет их! Вы могли бы переместить весь char *do_something(char *dest, const char *src) { ... } функция выше первого вызова, и компилятор тогда точно знал бы, как выглядит функция, и не должен был бы догадываться.

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

C99 и C11

полезно отметить, что правила немного отличаются в новых версиях стандарта C. В более ранних версиях (C89 и K&R) компилятор действительно б угадайте типы во время вызова функции (и компиляторы K&R-era часто даже не предупредят вас, если они ошибаются). C99 и C11 оба требуют, чтобы объявление функции / прототип должен предшествовать первому но многие современные компиляторы C-в основном для обратной совместимости с более ранним кодом-будут только предупредить о пропавшем прототипе и не считают это ошибкой.

когда вы не даете прототип для функции перед ее использованием, C предполагает, что она принимает любое количество параметров и возвращает int. Поэтому, когда вы впервые пытаетесь использовать do_something, это тип функции, которую ищет компилятор. Это должно привести к появлению предупреждения о "неявном объявлении функции".

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

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

убедитесь, что типы в объявлении функции были объявлены первыми.

/* start of the header file */
.
.
.
struct intr_frame{...}; //must be first!
.
.
.
void kill (struct intr_frame *);
.
.
.
/* end of the header file */

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