Прежде всего позвольте мне сказать, что это ужасный способ кодирования.
Тем не менее, давайте посмотрим, как это работает:
extern system, puts;
Это говорит о том, что system
и puts
определены где-то еще, а компоновщик позаботится дать вам свой адрес. Компилятор не знает, какой у них тип, поэтому gcc по крайней мере предполагает, что это int
и выдает предупреждение. Я не могу найти предложение в стандарте, в котором говорится, является ли это четко определенным, определяемым реализацией или неопределенным поведением, но, безусловно, это плохо. Это особенно плохо, потому что эти символы не являются int
. Я нашел это, хотя (C11, Приложение J.2 - Неопределенное поведение):
два заявления одного и того же объекта или функции указать типы, которые не совместимы (6.2.7).
Существует также вероятность того, что int
слишком мал, чтобы удерживать указатель. Нет конца тому, насколько это плохо.
Итак, давайте посмотрим, в памяти, как это выглядит:
system (as 4-byte int) system (as function)
BYTE1 INSTRUCTION1
BYTE2 INSTRUCTION2
BYTE3 INSTRUCTION3
BYTE4 INSTRUCTION4
INSTRUCTION5
INSTRUCTION6
INSTRUCTION7
INSTRUCTION8
...
Так что линкер делает, чтобы сделать адрес system
то же на каждом скомпилированный объекте (эффективно связывая их вместе). С другой стороны, содержимое system
зависит от его типа, о котором линкер не знает и не заботится.
Так что в вашем файле, если сразу же после того, как:
extern system, puts;
Вы пишете:
printf("%d\n", system);
вы получите первые пару инструкции функции system
, как если бы он был int
и распечатать его , Что плохо, и не делайте этого.
С другой стороны, как я уже сказал, адрес из system
, как определено extern
в вашей программе так же, как реальный system
функции. Так что если вы берете адрес:
&system
и бросьте его правильный тип:
(void(*)(char*))&system
вы получаете указатель на функцию, указывая на реальную system
функции.Обратите внимание, что system
просто теперь int
, и мы собираемся назвать его как функцию, это само по себе неопределенное поведение:
C11 Приложение J-2 (неопределенное поведение):
Объект имеет его сохраненное значение доступно для доступа, отличное от lvalue допустимого типа (6.5).
С11 6.5 (курсив мой):
6. Эффективный тип объекта для доступа к его сохраненным значением является объявленный тип объекта, если таковые имеются. ...
7. Объект должен быть его сохраненное значение доступно только с помощью Lvalue выражения, имеющих один из следующих типов: 88)
- типа, совместимого с эффективным типом объекта,
- квалифицированной версия типа, совместимого с эффективным типом объекта,
- типа, который является знаком или без знака типа, соответствующим эффективного типа объекта,
- типа, который является знаком или без знака тип соответствия ding до квалифицированной версии эффективного типа объекта,
- тип агрегата или объединения, который включает один из вышеупомянутых типов среди его членов (включая рекурсивно, элемент субагрегата или содержащий объединение) или
- тип символа.
Начало прошли все это, теперь у вас есть указатель на функцию, содержащую реальный адрес system
. Затем вы называете это своим содержанием. То же самое делается с puts
.
Это было плохо. Вот как это на самом деле должно быть сделано:
#include <stdlib.h>
#include <stdio.h>
int main(int argv,char **argc)
{
char buf[256];
/* system was unused */
strcpy(buf,argc[1]);
puts(argc[2]);
return 0; /* 0 means no error */
}
Кроме благости использования реализации предоставленных файлов заголовков, давайте посмотрим, как это один вместо фактически работает.
Внутри файлов заголовков у вас много строк со многими объявлениями. Один для puts
может выглядеть следующим образом:
int puts(const char *s);
Во-первых, обратите внимание, как указатель на функцию в вашем коде на самом деле есть функция подписи не так? Это плохо. Это плохо, потому что, вызывая функцию void
, вызывающему абоненту не нужно предоставлять пространство для возвращаемого значения, а puts
ожидает, что он будет там, поэтому puts
напишет возвращаемое значение, где оно ожидает его найти, но на самом деле это пространство принадлежит чему-то другому. Это называется повреждением стека.
Во всяком случае, когда компилятор видит следующую строку:
int puts(const char *s);
он будет знать, что puts
является внешнее определение:
C11 6.2.2 (курсив мой):
5. Если декларация идентификатора для функции не имеет спецификатора класса хранения, ее l Нейтралирование определяется точно так, как если бы оно было объявлено спецификатором класса хранения extern. Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь является внешней.
он будет знать, что это функция, и какова ее подпись.
Итак, просто позвонив fputs(...)
, компилятор делает все правильно.
Еще один вопрос: & system & & puts - это адреса этих встроенных функций, когда они скомпилированы в программу правильно? Кроме того, в чем преимущество объявления хорошо известной функции extern и создания указателя функции? – skrillac
Нет смысла писать такой код. Вы должны включить соответствующие заголовки, чтобы получить правильные прототипы. Этот код выглядит так, как будто он написан, чтобы продемонстрировать что-то конкретное. Где ты взял это? –
В качестве аргумента аргументы 'main' отменяются:' int' должен быть 'argc' (аргумент count), а' char ** 'должен быть' argv' (vector vector) , –