Почему в C возникают конфликтующие типы для функции?

Я использую приведенный ниже код:

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

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

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

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

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

Почему?

Ответ 1

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

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

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

Ответ 2

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

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

int do_something(char *, char *)

Однако позже в коде вы явно объявляете do_something как

char *do_something(char *, const char *)

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

Ответ 3

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

Вам нужно что-то вроде

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

перед печатью.

Пример:

#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.

Ответ 4

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

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

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

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

Ответ 6

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

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

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

Сфокусируйтесь на этой строке:

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

Вы можете четко видеть, что функция do_something не объявлена!

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

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));

Приветствия;)

Ответ 7

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

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

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

Ответ 8

A C-декларация функций

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

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

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

Урок истории

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

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

Быстро переходите к сегодняшнему дню, и мы не совсем в одной лодке. C вырос, и в нем много программистов, которые не являются волшебниками, и для размещения их (и для размещения всех, кто регулярно использовал lint) в любом случае, компиляторы взяли на себя многие из способностей, которые были ранее часть lint - особенно часть, где они проверяют ваш код, чтобы обеспечить его безопасный тип. Ранние компиляторы C позволят вам писать int foo = "hello";, и он просто blithely назначает указатель на целое число, и вам решать, чтобы вы не делали ничего глупого. Современные компиляторы 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() - not void - вот почему многие современные стандарты кода рекомендуют всегда помещать void in для аргументов, если они действительно не должны быть.)

Исправление

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

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

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-времена часто даже не предупредили вас, если они были неправы). C99 и C11 требуют, чтобы объявление функции/прототип должно предшествовать первому вызову, и это ошибка, если это не так. Но многие современные компиляторы C - в основном для обратной совместимости с более ранним кодом - будут предупреждать только о пропущенном прототипе и не считать его ошибкой.

Ответ 9

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



/* start of the header file */
.
.
.

struct intr_frame{...}; //must be first!
.
.
.

void kill (struct intr_frame *);
.
.
.

/* end of the header file */

Ответ 10

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