Рассмотрим модульную расширяемую систему. Создадим первый модуль, в котором определим константу, и скомпилируем его. Создадим второй модуль, который импортирует первый модуль и использует определённую ранее константу. Скомпилируем второй модуль. А теперь возьмем первый модуль, изменим в нем значение константы на другое и скомпилируем его. Второй модуль перекомпилировать не будем. Запустим на выполнение старый второй модуль вместе с новым первым. Что произойдет? Что реально происходит в таких модульных расширяемых системах как Oberon и .NET я сообщу позже, а сейчас давайте подумаем что должно происходить. Идеальным было бы так, чтобы второй модуль использовал новое значение константы из первого модуля. Не так ли? Что этому мешает? Я думаю, что как минимум на это есть две причины. Во-первых, при компиляции второго модуля константа могла быть нужна компилятору, например, для определения размера переменных в том случае если бы она была использована, например, как размер (не динамического) массива. Во-вторых, используя константу вместо переменной компилятор может осуществить оптимизацию. Первая причина в отличие от второй является фатальной. Выходит, мы не можем ожидать от второго модуля использование константы из первого модуля? Но, не кажется ли Вам, что это какой-то бред? Второй модуль использует константу из первого модуля, которую использовать не может — точно бред, не так ли? Выходит в модульных расширяемых системах просто напросто должно быть запрещено экспортирование / имортирование констант! Ибо это просто невозможно физически реализовать.
Что реально происходит в Oberon (BlackBox 1.5 BETA):
MODULE Module1;
CONST N* = 10; (* Потом меняем ее на 20 *)END Module1.
MODULE Module2;
IMPORT StdLog, Module1;
PROCEDURE Do*;
BEGIN
IF Module1.N = 10 THEN
StdLog.String("Module1.N = 10"); StdLog.Ln
ELSE
StdLog.String("Module1.N # 10"); StdLog.Ln
END
END Do;
END Module2.
При попытке запустить на выполнение старого второго модуля с новым первым происходит ошибка:
"command error: object Module1.N inconsistently imported from Module2"
Что реально происходит в .NET (1.1)
namespace Test
{
public sealed class Module1
{
public const int N = 10; /* Потом меняем ее на 20 */
}
}
namespace Test
{
class Program
{
static void Main(string[] args)
{
if(Module1.N == 10)
{
System.Console.WriteLine("Module1.N = 10");
}
else
{
System.Console.WriteLine("Module1.N # 10");
}
System.Console.ReadLine();
}
}
}
Запускаем на выполнение старый второй модуль с новым первым, в консоли преспокойно печатается какая-то ерунда:
"Module1.N = 10"
ведь в новом первом модуле Module1.N = 20. Совсем не ожиданно, однако...
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Запускаем на выполнение старый второй модуль с новым первым, в консоли преспокойно печатается какая-то ерунда: СГ>
СГ>"Module1.N = 10"
СГ>ведь в новом первом модуле Module1.N = 20. Совсем не ожиданно, однако...
Извини, не прочел все эссе целиком (в лом).
Но, думаю я и так угадаю. Спецификации нужно читать. С целью увеличения быстродействия дотнет копирует (propagate) константы в применяемые модули. Другими словами константа — это ее значение.
Если тебе нужна неизменяемая переменная которая не будет подставляться по месту, то пользуйся модификатором readonly:
class Test
{
public readonly int Test;
}
... << RSDN@Home 1.1.4 beta 7 rev. 466>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, gbear, Вы писали:
G>Константа, она на то и константа. Если бы вычисление её значения переводилось бы в run-time, то это была бы уже не константа. Так... свойство.
Я про то и говорю — значит в модульных расширяемых системах должен быть запрещен импорт / экспорт констант!
Сергей Губанов wrote:
> # Выходит в модульных расширяемых системах просто напросто должно быть > запрещено экспортирование / имортирование констант! Ибо это просто > невозможно физически реализовать.
В нормальных языках (то есть в C/C++) такой проблемы нет — при
модификации константы в h-файле просто все зависимое от нее
перекомпилируется.
В C# тоже нет проблемы — при изменении сборки меняется ее контрольная
сумма, и все зависимое от нее будет перекомпилировано. При попытке
совместить несовместимые сборки — получится exception.
Здравствуйте, VladD2, Вы писали:
VD> Спецификации нужно читать.
Большое спасибо, но я ни сколько не сомневался, что данное поведение обязательно описано в спецификации.
Однако поставленный вопрос, собственно, заключается в смысле экспорта / импорта констант в модульных расширяемых системах, ведь это не возможно реализовать физически.
Здравствуйте, Cyberax, Вы писали:
C>В нормальных языках (то есть в C/C++) такой проблемы нет — при C>модификации константы в h-файле просто все зависимое от нее C>перекомпилируется.
Эти языки не модульные. Нет модулей — нет проблем.
C>В C# тоже нет проблемы — при изменении сборки меняется ее контрольная C>сумма, и все зависимое от нее будет перекомпилировано. При попытке C>совместить несовместимые сборки — получится exception.
Я только что привел пример где этого exception нет, зато преспокойно распечатывается "Module1.N = 10", в то время когда Module1.N = 20.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Здравствуйте, Cyberax, Вы писали:
C>>В нормальных языках (то есть в C/C++) такой проблемы нет — при C>>модификации константы в h-файле просто все зависимое от нее C>>перекомпилируется.
СГ>Эти языки не модульные. Нет модулей — нет проблем.
C>>В C# тоже нет проблемы — при изменении сборки меняется ее контрольная C>>сумма, и все зависимое от нее будет перекомпилировано. При попытке C>>совместить несовместимые сборки — получится exception.
СГ>Я только что привел пример где этого exception нет, зато преспокойно распечатывается "Module1.N = 10", в то время когда Module1.N = 20.
Вы не использовали strong-name для своей сборки, то есть не рассчитывали её контрольную сумму и не подписывали её — а, следовательно, это ваша сугубо личная проблема. Потому что такие сборки годятся только для написания тестов и отладочных версий приложений. Выпускаемая в свет сборка должна иметь строгое имя...
Компьютер сделает всё, что вы ему скажете, но это может сильно отличаться от того, что вы имели в виду.
На самом деле эту проблему можно решить. Особенно для таких систем, как .Net. Там есть VM, которая делает всякие служебные действия во время выполнения программы. Например, размеры структур, объектов и т.п. считаются во время выполнения джит компилятором. Тогда-же считаются и размеры стековых фреймов. Так что никакой проблемы-то и нет. Джит с таким же успехом может посчитать все константы и вписать значения куда нужно в коде и т.п.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Однако поставленный вопрос, собственно, заключается в смысле экспорта / импорта констант в модульных расширяемых системах, ведь это не возможно реализовать физически.
Для многих задач быстродействие важно, а межмодульная жизнь констрант нет. В общем, есть выбор. Хочешь используешь константны хочешь перменные с readonly.
... << RSDN@Home 1.1.4 beta 7 rev. 466>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Сергей Губанов wrote:
> C>В нормальных языках (то есть в C/C++) такой проблемы нет — при > C>модификации константы в h-файле просто все зависимое от нее > C>перекомпилируется. > Эти языки не модульные. Нет модулей — нет проблем.
Модульные, просто модули делаются по-другому.
> C>В C# тоже нет проблемы — при изменении сборки меняется ее контрольная > C>сумма, и все зависимое от нее будет перекомпилировано. При попытке > C>совместить несовместимые сборки — получится exception. > Я только что привел пример где этого exception нет, зато преспокойно > распечатывается "Module1.N = 10", в то время когда Module1.N = 20.
Ну да, в Обероне. В нормальных системах такой проблемы не будет.
Здравствуйте, Mr. None, Вы писали:
MN>Здравствуйте, Сергей Губанов, Вы писали:
СГ>>Здравствуйте, Cyberax, Вы писали:
C>>>В нормальных языках (то есть в C/C++) такой проблемы нет — при C>>>модификации константы в h-файле просто все зависимое от нее C>>>перекомпилируется.
СГ>>Эти языки не модульные. Нет модулей — нет проблем.
C>>>В C# тоже нет проблемы — при изменении сборки меняется ее контрольная C>>>сумма, и все зависимое от нее будет перекомпилировано. При попытке C>>>совместить несовместимые сборки — получится exception.
СГ>>Я только что привел пример где этого exception нет, зато преспокойно распечатывается "Module1.N = 10", в то время когда Module1.N = 20.
MN>Вы не использовали strong-name для своей сборки, то есть не рассчитывали её контрольную сумму и не подписывали её — а, следовательно, это ваша сугубо личная проблема. Потому что такие сборки годятся только для написания тестов и отладочных версий приложений. Выпускаемая в свет сборка должна иметь строгое имя...
Согласен. Только хочу добавить, что при подписывании сборки никакого процесса рассчета ее контрольной суммы не происходит (это нужно только студии). И не факт, что это может спасти. Если из сборки берется только константа, то такая сборка вовсе не нужна приложению и в процессе работы она не будет загружена. При этом приложение будет работать как раньше.
Сергей Губанов, а зачем использовать константы, если, как правильно указал VladD2, есть readonly поля?
Ракот wrote:
> MN>Вы не использовали strong-name для своей сборки, то есть не > рассчитывали её контрольную сумму и не подписывали её — а, > следовательно, это ваша сугубо личная проблема. Потому что такие > сборки годятся только для написания тестов и отладочных версий > приложений. Выпускаемая в свет сборка должна иметь строгое имя... > Согласен. Только хочу добавить, что при подписывании сборки никакого > процесса рассчета ее контрольной суммы не происходит (это нужно только > студии).
Происходит, рассчет контрольной суммы — это часть процесса подписывания.
Подписанная либа получает GUID, по которому ее и находят остальные сборки.
Подписанную сборку изменить нельзя, так что при изменении константы
придется ее переподписать и сгенерировать новый GUID. Ну а от этого уже
остальные сборки пересоберутся.
[... skipped ...]
C>Происходит, рассчет контрольной суммы — это часть процесса подписывания. C>Подписанная либа получает GUID, по которому ее и находят остальные сборки.
C>Подписанную сборку изменить нельзя, так что при изменении константы C>придется ее переподписать и сгенерировать новый GUID. Ну а от этого уже C>остальные сборки пересоберутся.
А если я тем же ключом подпишу? Ведь тогда, по идее, строгое имя сборки не поменяется (если я преднамеренно не поменяю номер версии сборки, что я, конечно же, сделаю в случае её изменения ).
Oyster wrote:
> C>Подписанную сборку изменить нельзя, так что при изменении константы > C>придется ее переподписать и сгенерировать новый GUID. Ну а от этого уже > C>остальные сборки пересоберутся. > А если я тем же ключом подпишу? Ведь тогда, по идее, строгое имя > сборки не поменяется (если я преднамеренно не поменяю номер версии > сборки, что я, конечно же, сделаю в случае её изменения ).
Ну если постараться, то можно что угодно сломать. Достаточно, чтобы
сильное имя осталось тем же — этого можно добиться, если очень захотеть.
Здравствуйте, Cyberax, Вы писали:
C>Oyster wrote:
>> C>Подписанную сборку изменить нельзя, так что при изменении константы >> C>придется ее переподписать и сгенерировать новый GUID. Ну а от этого уже >> C>остальные сборки пересоберутся. >> А если я тем же ключом подпишу? Ведь тогда, по идее, строгое имя >> сборки не поменяется (если я преднамеренно не поменяю номер версии >> сборки, что я, конечно же, сделаю в случае её изменения ).
C>Ну если постараться, то можно что угодно сломать. Достаточно, чтобы C>сильное имя осталось тем же — этого можно добиться, если очень захотеть.
Без приватного ключа? Нелегко это будет... А вот создателю сборки можно без проблем менять что угодно и когда угодно — у него ключ есть и поэтому он всегда может переподписать.
Oyster wrote:
> C>Ну если постараться, то можно что угодно сломать. Достаточно, чтобы > C>сильное имя осталось тем же — этого можно добиться, если очень захотеть. > Без приватного ключа? Нелегко это будет... А вот создателю сборки > можно без проблем менять что угодно и когда угодно — у него ключ есть > и поэтому он всегда может переподписать.
С приватным ключем естественно, без него переподписать не получится.
Здравствуйте, VladD2, Вы писали:
VD>Но, думаю я и так угадаю. Спецификации нужно читать. С целью увеличения быстродействия дотнет копирует (propagate) константы в применяемые модули. Другими словами константа — это ее значение.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Модульная загадка про константу
СГ>[.skip.]
Согласен, что значение константы должно быть частью метаинформации и анализироваться компилятором и рантаймом.
Согласен, что в Обероне лучше, чем в C# и Java. Хотя, по-моему, совсем хорошо было бы, если бы эта проверка выполнялась непосредственно перед выполнением кода, использующего константу. Например, может быть атрибут у метода, содержащего вызов, "использую такую-то константу из такого-то модуля с таким-то значением". При загрузке кода метода атрибут можно было бы прочитать и убедиться, что значения констант совпадают, иначе кинуть exception.
Про фатальность ошибок не согласен. На самом деле — оба типа могут быть как "фатальными", так и не "фатальными".
С рассуждением про то, что константы нельзя использовать в модульных языках не согласен. Могу ещё одно такое же рассуждение толкнуть: допустим мы объявили в первом модуле метод, а во втором его вызвали. Компилятору нужно знать сигнатуру метода, чтобы сгенерировать код его вызова. Теперь добавим в метод параметр и перекомпилируем первый модуль, не компилируя второй. Запустим и, кто бы мог подумать, все фатально навернется при вызове! Выходит, в модульных системах должно быть запрещено использование методов. Чем не бред? Все потому, что выводы мало связаны с посылками, из которых сделаны. То же произошло и в Вашем рассуждении про константы.
Здравствуйте, Сергей Губанов, Вы описали
два случая, но есть ещё третий — Delphi.
Вроде оно вполне модульное.
Здесь и ошибок не выдаётся, в отличии от BlackBox, и компилируется всё правильно, в отличии от .NET.
Естественно это достигается перекомпиляцией второго модуля при изменении важной константы в первом. Впрочем, если в первом поменять что-либо менее важное, например функцию, или чего добавить, то перекомпиляции второго модуля не будет.
Хотя, если добавить третий модуль и совпадающие имена, то тут Delphi опускается до уровня .NET и для правильной компиляции требуется -B = Build all units.
Здравствуйте, Ракот, Вы писали:
Р>Сергей Губанов, а зачем использовать константы, если, как правильно указал VladD2, есть readonly поля?
Так и я про то: константы и модульность, получается, являются взаимоисключающими понятиями. То есть надо либо запретить импорт / экспорт констант, чтобы случайно не нарваться на неприятности, либо явно проверять согласованность импорта при каждой загрузке (как, например, было сделано в первом примере который я привел).
Здравствуйте, andyJB, Вы писали:
JB>С рассуждением про то, что константы нельзя использовать в модульных языках не согласен. Могу ещё одно такое же рассуждение толкнуть: допустим мы объявили в первом модуле метод, а во втором его вызвали. Компилятору нужно знать сигнатуру метода, чтобы сгенерировать код его вызова. Теперь добавим в метод параметр и перекомпилируем первый модуль, не компилируя второй. Запустим и, кто бы мог подумать, все фатально навернется при вызове! Выходит, в модульных системах должно быть запрещено использование методов. Чем не бред? Все потому, что выводы мало связаны с посылками, из которых сделаны. То же произошло и в Вашем рассуждении про константы.
Думаю, что Вы правы.
Однако, .NET на неправильный метод ругнулся бы, чего он тогда на константу не ругается?
Здравствуйте, exp_1, Вы писали:
_>Здравствуйте, Сергей Губанов, Вы описали _>два случая, но есть ещё третий — Delphi. _>Вроде оно вполне модульное.
Нет. Дельфийские unit-ы модулями не являются. Модули — это не только единицы раздельной независимой компиляции, но еще и (динамически загружаемые/выгружаемые) единицы исполнения, а именно exe или dll файлы (это если под Windows).
.dcu-шник, т.е. то во что компилируются unit-ы единицей исполнения не является (тем более динамически загружаемой/выгружаемой).
Здравствуйте, Andrei N.Sobchuck, Вы писали:
VD>>Но, думаю я и так угадаю. Спецификации нужно читать. С целью увеличения быстродействия дотнет копирует (propagate) константы в применяемые модули. Другими словами константа — это ее значение.
ANS>А как же хвалёный JIT?
Это джит и делает. Почитай на досуге про компиляторыные оптимизации... есть такая штука constant folding.
... << RSDN@Home 1.1.4 beta 7 rev. 466>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Сергей Губанов, Вы писали:
СГ> exe или dll файлы
С ними ещё проще. Там нет "экспортирование / имортирование констант!"
Там есть функции, которые согласно Вашим требованиям иметь возможность изменять (например количество параметров) без общей переделки/перекомпиляции тоже нельзя использовать.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Однако, .NET на неправильный метод ругнулся бы, чего он тогда на константу не ругается?
О своем понимании того, что должно происходить, я уже писал. Оберон тут ближе всех к тому, что должно быть, но тоже не дотягивает до идеала. Относительно того, почему ребята из Microsoft поленились, как и ребята из Sun со static final, ответить не могу. Подозреваю, что, так как в Java такая ситуация большинство устраивала, в C# просто не стали заморачиваться.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Так и я про то: константы и модульность, получается, являются взаимоисключающими понятиями. То есть надо либо запретить импорт / экспорт констант, чтобы случайно не нарваться на неприятности, либо явно проверять согласованность импорта при каждой загрузке (как, например, было сделано в первом примере который я привел).
Для таких случаев есть тулзы типа FxCop. Если уж в нем нельзя задать такое правило то тулза проверяющая наличие в сборке публичных констант пишется минут за 10. Вводить же запрет на уровне языка\среды это через чур жестко ИМХО.
Здравствуйте, exp_1, Вы писали:
_>Здравствуйте, Сергей Губанов, Вы писали:
СГ>> exe или dll файлы
_>С ними ещё проще. Там нет "экспортирование / имортирование констант!"
Здравствуйте, Cyberax, Вы писали:
>> распечатывается "Module1.N = 10", в то время когда Module1.N = 20.
C> В нормальных системах такой проблемы не будет.
То есть .NET не нормальная система раз она печатает "Module1.N = 10", в то время когда Module1.N = 20.
В общем-то, я согласен с Вами. Мне тоже это кажется не нормальным.
Сергей Губанов wrote:
>>> распечатывается "Module1.N = 10", в то время когда Module1.N = 20. > C> В нормальных системах такой проблемы не будет. > То есть .NET /не нормальная/ система раз она печатает "Module1.N = > 10", в то время когда Module1.N = 20. > В общем-то, я согласен с Вами. Мне тоже это кажется не нормальным.
В .NET в сборке записываются версии сборок-зависимостей. Если при
изменении константы менять версию сборки — все будет нормально.
Здравствуйте, Сергей Губанов, Вы писали:
СГ>Модульная загадка про константу
СГ>Рассмотрим модульную расширяемую систему. Создадим первый модуль, в котором определим константу, и скомпилируем его. Создадим второй модуль, который импортирует первый модуль и использует определённую ранее константу. Скомпилируем второй модуль. А теперь возьмем первый модуль, изменим в нем значение константы на другое и скомпилируем его. Второй модуль перекомпилировать не будем. Запустим на выполнение старый второй модуль вместе с новым первым. Что произойдет? Что реально происходит в таких модульных расширяемых системах как Oberon и .NET я сообщу позже, а сейчас давайте подумаем что должно происходить. Идеальным было бы так, чтобы второй модуль использовал новое значение константы из первого модуля. Не так ли? Что этому мешает? Я думаю, что как минимум на это есть две причины. Во-первых, при компиляции второго модуля константа могла быть нужна компилятору, например, для определения размера переменных в том случае если бы она была использована, например, как размер (не динамического) массива. Во-вторых, используя константу вместо переменной компилятор может осуществить оптимизацию. Первая причина в отличие от второй является фатальной. Выходит, мы не можем ожидать от второго модуля использование константы из первого модуля? Но, не кажется ли Вам, что это какой-то бред? Второй модуль использует константу из первого модуля, которую использовать не может — точно бред, не так ли? Выходит в модульных расширяемых системах просто напросто должно быть запрещено экспортирование / имортирование констант! Ибо это просто невозможно физически реализовать.
Вряд ли это — проблема.
Если разработчик dll-модуля в .Net меняет значение публичной константы, то он меняет его публичный интерфейс.
А это низззя. Никогда. Это нарушение обратной совместимости.
Надо новую константу добавить, и всех делов.
А если уж хочется поменять константу, то извольте задать сборке новую версию, несовместимую (была 1.xx.x — стала 2.0.0 — major number поменялся — обратной совместимости нет) — тогда сборка по старому strong name не загрузится, и её пользователям придётся адаптироваться к ней и перекомпилироваться.
Вопрос только в том — если разработчик модуля такой лопух, и меняет значение константы, или сигнатуры метода, а strong name не меняет, и сборка у клиента цепляется нормально, а потом где-то в середине кода выкидывается исключение о несоответствии сигнатуры метода, или логика рушится из-за изменения константы, то хорошо бы от этого защититься как-то? Чтобы, например, в модуле хранились описания всех классов с сигнатурами методов, и констант — со значениями — для каждого модуля, который импортируется этим. И при загрузке модуля проверять все его зависимости, со всеми метаданными.
Но какие же это будут тормоза...
Это же надо будет именно при загрузке модуля загружать все его зависимости, и проверять их.
Чтобы не получить козью морду при динамической загрузке модуля где-то в середине важной процедуры.
На такую безопасность ценой таких тормозов вряд ли кто пойдёт.
Такое, наверное, надо отслеживать верификаторами вроде FxCop или, например, линковать экзешник со всеми его зависимыми dll-модулями в один большой экзешник, а по линковке и проверять. Тогда в слинкованном экзешнике будут все зависимости, и все они будут правильными.
Здравствуйте, Cyberax, Вы писали:
C>Происходит, рассчет контрольной суммы — это часть процесса подписывания. C>Подписанная либа получает GUID, по которому ее и находят остальные сборки.
Остальные сборки находят сборку по связке имя + версия + культура + публичный ключ.
C>Подписанную сборку изменить нельзя, так что при изменении константы C>придется ее переподписать и сгенерировать новый GUID. Ну а от этого уже C>остальные сборки пересоберутся.
Процесс пересобирания — это компайлтайм. Процесс нахождения сборок — это runtime. Процесс подписывания — это где-то по-середине. Ты уверен насчет ГУИД?
Ракот wrote:
> C>Происходит, рассчет контрольной суммы — это часть процесса > подписывания. > C>Подписанная либа получает GUID, по которому ее и находят остальные > сборки. > Остальные сборки находят сборку по связке имя + версия + культура + > публичный ключ.
Здравствуйте, Cyberax, Вы писали:
C>Ракот wrote:
>> C>Происходит, рассчет контрольной суммы — это часть процесса >> подписывания. >> C>Подписанная либа получает GUID, по которому ее и находят остальные >> сборки. >> Остальные сборки находят сборку по связке имя + версия + культура + >> публичный ключ.
C>Сильное имя — это вообще-то GUID и есть.
Now all the CLR team needed was to figure out an algorithm for generating a unique assembly name. Perhaps part of the name could be a large random number akin to a GUID—maybe even larger than 16 bytes, to gain some extra collision resistance. This would protect you against someone accidentally picking the same identifier that you happened to be using to name your assemblies. But it wouldn't protect you against a bad guy purposely trying to make a Trojan horse assembly that looked just like yours. So the CLR team made an interesting decision. Instead of simply using a large random number, it decided to use a 1024-bit RSA public key, which is a 128-byte number constructed from two very large, random prime numbers multiplied together.
Здравствуйте, GarryIV, Вы писали:
GIV>Для таких случаев есть тулзы типа FxCop. Вводить же запрет на уровне языка\среды это через чур жестко ИМХО.
Запрет — не нужен, конечно, а вот warning нужен обязательно. Ибо спецификация спецификацией, а с логикой такое поведение не дружит , а потому — засада, Сергей правильно заметил.