2015-09-16 2 views
5

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

uses 
    Unit2, Unit3; 

{$R *.lfm} 
{ TForm1 } 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
    ShowMessage(IntToStr(CallMe(5))); 
end; 

unit Unit2; 
{$mode objfpc}{$H+} 
interface 
uses 
    Classes, SysUtils; 
function CallMe(A: Integer) : Integer; 
implementation 
function CallMe(A: Integer) : Integer; 
begin 
    Result := A * 2; 
end; 
end. 

unit Unit3; 
{$mode objfpc}{$H+} 
interface 
uses 
    Classes, SysUtils; 
function CallMe(A: Integer) : Integer; 
implementation 
function CallMe(A: Integer) : Integer; 
begin 
    Result := A * -1; 
end; 
end. 
+5

Это по дизайну: он называет тот, который компилятор видел во время компиляции. Если вы хотите позвонить другому, добавьте имя единицы, за которым следует точка. – MartynA

+0

Спасибо. Я хотел бы узнать мотивацию этого дизайна. Это дает возможность для ошибок. Предположим, что основной блок первоначально использовал CallMe от Unit2, затем программист B входит и добавляет unit3, потому что ему нужна какая-то функция оттуда и не знает, что он случайно заменил CallMe чем-то другим (представьте себе длинную единицу с большим количеством кода). Он компилируется и запускается. Никакого предупреждения нет. Я бы предпочел ошибку компилятора, чем проблему времени выполнения, и я не хочу, чтобы вызывали вызовы AVeryLongLibraryName.FunctionName и просматривали каждый вызов в каждом включенном модуле для двусмысленного вызова. –

+1

Любой язык программирования дает возможность для ошибок. Все дело в том, что вы должны знать, что делаете. Существуют внешние инструменты, которые дают вам подсказки по этим случаям. –

ответ

13

От documentation:

Если две единиц объявить переменную, константу, типа, процедуру или функцию с тем же именем, компилятор использует один из блока, указанные последними в пункте изеза , (Чтобы получить доступ к идентификатор из другого блока, вы должны добавить спецификатор:. UnitName.Identifier) ​​

+0

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

+0

Я жил с таким поведением со времен TP4 и никогда не сталкивался с проблемами при столкновении имен. Для указания этого условия нет подсказки/предупреждения/ошибки. Однако понимание кода может помочь, показывая, к какому модулю принадлежат функции. –

+0

@ user2304430: Невозможно превратить его в ошибку, потому что это не ошибка. –

2

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

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

[dcc32 Error] test.dpr: E2251 Ambiguous overloaded call to 'bla' 
    Unit1.pas(8): Related method: procedure bla; 
    Unit2.pas(8): Related method: procedure bla; 

, если у вас есть две различные сигнатуры, то будет выбрать наиболее подходящий один.

Перекрестная перегрузка - это более новая функция, но я не помню точно, когда. Я думаю, это D2006.