Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Прочее";
Текущий архив: 2013.12.08;
Скачать: [xml.tar.bz2];

Вниз

указание Out параметра зануляет переменную?   Найти похожие ветки 

 
EuSet   (2013-06-11 17:31) [0]

Интересное поведение компилятора. Возьмем пример:

procedure testString(out x: string);
begin
 beep;
end;

procedure testInt(out i: integer);
begin
 beep;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
 s: string;
 a: integer;
begin
 s := "sgsg";
 testString(s);
 ShowMessage(s);

 a := 123;
 testInt(a);
 ShowMessage(IntToStr(a));
end;


При выводе строки выведется ПУСТАЯ строка! То есть, произойдет зануление переданной переменной.

А вот в случае с integer ничего такого не произойдет - выведется установленное значение "123".

Что это за логика?!


 
брат Птибурдукова   (2013-06-11 17:33) [1]

Строки — отдельная епархия. В целом, нет никакого зануления.


> Что это за логика?!
Была книжка (не "глазами хакера"), где эта логика подробно рассматривалась, но сходу её название не вспомню. Что-то там не то "секреты", не то "тонкости"…


 
EuSet   (2013-06-11 17:36) [2]

Вообще, такая штука выявилась на функции аля:

function GetCard(const prefillData: TMyData; out resultData: TMyData): boolean;

То есть, достаточно банально - отображается карточка, функция возвращает введенные на карточке данные. При этом имеется возможность заполнить карточку предварительными данными.

И соответственно, когда кто-то вызывает функцию в виде:

> GetCard(MyVal, MyVal)

то при входе в функцию значение полей структуры prefillData оказываются заниленными! Причем, странным образом - строки как показано в топике затираются, а вот числовые значения, допустим, нет...
странно.


 
EuSet   (2013-06-11 17:37) [3]


> Строки — отдельная епархия. В целом, нет никакого зануления.

почему же строки так волшебно зануляются? Причем, как непосредственно переданные строки, так и строки внутри сложных структур...
бред какой-то...


 
EuSet   (2013-06-11 17:39) [4]

и так во всех версиях дельфи? Вообще, хоть объяснить это поведение бы...


 
MBo ©   (2013-06-11 17:47) [5]

Хелп гласит:
Out Parameters
An out parameter, like a variable parameter, is passed by reference. With an out parameter, however, the initial value of the referenced variable is discarded by the routine it is passed to. The out parameter is for output only; that is, it tells the function or procedure where to store output, but doesn"t provide any input.

For example, consider the procedure heading:

procedure GetInfo(out Info: SomeRecordType);
When you call GetInfo, you must pass it a variable of type SomeRecordType:

var MyRecord: SomeRecordType;
  ...
GetInfo(MyRecord);
But you"re not using MyRecord to pass any data to the GetInfo procedure; MyRecord is just a container where you want GetInfo to store the information it generates. The call to GetInfo immediately frees the memory used by MyRecord, before program control passes to the procedure.


 
EuSet   (2013-06-11 17:53) [6]

Да, я прочитал хелп. Но поведение все равно странное.
Почему числа не зануляются тогда?


 
брат Птибурдукова   (2013-06-11 17:57) [7]


> почему же строки так волшебно зануляются?
Не волшебно. Просто неочевидно.
Строка — сложный тип с неявным временем жизни и подсчётом ссылок. Упрощённо: когда ты говоришь "мне нужна строка", выделяется место под строку и говорится, что строка содержит ноль символов. Для простых типов такая подготовительная работа не проводится, и локальные переменные имеют то значение, которое лежало в памяти до того, как этот участок стал считаться переменной. Если заменишь делфовые строки на PChar, волшебное обнуление пропадёт.


 
MBo ©   (2013-06-11 18:04) [8]

для out-параметра выполняется "финализация" (внутренние процедуры вроде FinalizeRecord) - зачистка динамических структур  - строк, интерфейсов, динмассивов. Статические поля финализовать не нужно.


 
EuSet   (2013-06-11 21:16) [9]


> зачистка динамических структур

но при этом на классы опять же не распространяется?


 
Sha ©   (2013-06-11 22:53) [10]

EuSet, мысленно поставь себя на место разработчика компилятора.
Понимаю, что трудно. Но попытайся ))
Рано или поздно тебе придется где-то финализировать вернувшуюся после вызова out-переменную.
Если в ней будет мусор, ее финализировать не удастся.


 
EuSet   (2013-06-12 00:05) [11]

Sha, не понял смысла поста.

О какой финализации идет речь? Инициализирует и финализирует переменную вызывающий код, это не забота функции.

По примеру "const" - это ведь передача как "var", но в теле функции не дается присвоить значение этой переменной. Так вот "out" на мой взгляд логичнее было бы сделать как "var", но с невозможностью считывать значение переменной.
Хотя это может породить некоторые неудобства, например после присвоения одного "out" параметра невозможность его использовать для расчета другого "out" параметра.

А сейчас работает совсем нелогично, на мой взгляд. Если передаем строчку - она "зануляется", ну ок... А вот если передаем число - не зануляется. Передаем TDateTime - тоже не зануляется. Динамический массив - зануляется. Класс - не зануляется. То есть, какой-то разброд и шатания. Имхо.

Плюс, мне кажется, я привел пример не такой уж редкой ситуации. Вполне нормальная функция:

MyFunc(inVal: TMyStruct; outVal: TMyStruct): boolean;

То есть, в функцию передают набор данных, если функция отработала успешно - она возвращает такой же набор данных, но уже "измененных" пользователем (допустим, это диалог на экране). Достаточно удобно пользоваться, можно в функцию передать разные структуры и иметь исходный набор данных и конечный набор данных. А если этого не требуется, то осуществить вызов:

MyFunc(MyVal, MyVal);

Что плохого? Но если функция объявлена как:

MyFunc(const inVal: TMyStruct; out outVal: TMyStruct): boolean;

то натыкаемся на ошибки. Потому что при входе в функцию считать значение из inVal невозможно, потому что она уже обнилена, поскольку та же переменная передана как outVal.
Хотя если TMyStruct представляет собой числа - то проблемы не будет. А вот если строки - то будет. Что еще больше запутывает.

Причем, непонятно кто должен следить за передачей. Разработчик функции MyFunc - вроде непричем, объявил входную переменную, объявил выходную переменную. Ну они одного типа - это да, бывает. Что он может сделать?

По сути, учитывать такой эффект должен ВЫЗЫВАЮЩИЙ функцию, но он в принципе тоже непричем, для него это черный ящик. Сказано же - передать инициализирующие значения, забрать результат - он так и сделал. Но решил сэкономить, использовав одну переменную.

В общем, мне это представляется неочевидным поведением.


 
Rouse_ ©   (2013-06-12 00:08) [12]


> EuSet   (12.06.13 00:05) [11]
> Sha, не понял смысла поста.
>
> О какой финализации идет речь? Инициализирует и финализирует
> переменную вызывающий код, это не забота функции.

Это ты из каких учебников нахватался?


 
Rouse_ ©   (2013-06-12 00:11) [13]


> В общем, мне это представляется неочевидным поведением.

Тебя спасет как минимум досконально погружение в справку.
Ты перемешал все что только можно и даже сделал вывод о нелогичности поведения, а всего-то нужен маленький RTFM и все сразу станет логично и очевидно.


 
Sha ©   (2013-06-12 00:12) [14]

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

Кстати, с предварительным вызовом LStrClr связан забавный баг.
Попробуй угадать, что будет выведено таким кодом:

procedure outproc(const s1: string; var s2: string; s3: string;
                 const s4: string; var s5: string; s6: string;
                 out s: string);
begin;
 s:=s1+"1"
   +s2+"2"
   +s3+"3"
   +s4+"4"
   +s5+"5"
   +s6+"6";
 end;

procedure TForm1.Button1Click(Sender: TObject);
var
 s: string;
begin;
 s:="s";
 outproc(s, s, s, s, s, s, s);
 ShowMessage(s);
 end;


 
Sha ©   (2013-06-12 00:15) [15]

> EuSet   (12.06.13 00:05) [11]
> Sha, не понял смысла поста.


Жаль. Я старался.
Посты [10] и [14] надо читать вместе. Тогда в них больше смысла )


 
Rouse_ ©   (2013-06-12 00:32) [16]


> Sha ©   (12.06.13 00:12) [14]

Интересная схема, ожидаемо - но подход к сути проблемы понравился :)


 
EuSet   (2013-06-12 01:06) [17]


> Инициализирует и финализирует
> > переменную вызывающий код, это не забота функции.
>
> Это ты из каких учебников нахватался?

а разве это не так?
Если мы объявим процедуру:

procedure MyProc(const a: TA; var b: TB; c: TC);

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

Вот с "out" - особенности, данный вопрос я и хочу уяснить в данном топике.

Более того, если мы объявим функцию:

function MyFunc(...): TMy;

то возвращаемое значение также инициализируется вызывающим кодом. Иными словами, если рассмотреть:

var
 My: TMy;
begin
 My := MyFunc(...);
...


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

Есть мнение, что у нас разная терминология и ты имел в виду нечто другое.


> Ты перемешал все что только можно и даже сделал вывод о
> нелогичности поведения

ну да, такое поведение мне кажется нелогичным. Оно может быть описано в справке - тогда такое поведение станет задокументированным. Но оно все равно может быть нелогичным.
Нелогичность я описал - допустим, переменная типа "string" - почему-то зануляется (то есть, в применении к строке - становится равной пусто). А вот переменная типа integer - почему то не зануляется.
Ну разве это логично, что в одном случае мы имеем изменение значения out переменной, а в другой случае нет?


 
EuSet   (2013-06-12 01:14) [18]


> Попробуй угадать, что будет выведено таким кодом:

да, и правда забавно. Вот это также на мой взгляд нелогично. Почему-то "const s1" оказывается пустой строкой, а вот "const s4: string" равняется значению "s".

Ну это, видимо, чистый баг. Я же говорил о другом немного.

Также я до сих пор не понял фразу:


> Рано или поздно тебе придется где-то финализировать вернувшуюся
> после вызова out-переменную

в чем проблема?

Пытаюсь смотреть с точки зрения компилятора. Ок, мы имеем вызов:

MyProc(out a: Ta);

то есть, то что передается в MyProc - уже неким образом инициализировано вызывающим кодом. Изменит ли код MyProc значение переменной - неважно, имхо, какая разница? все равно это можно финализировать по правилам delphi-компилятора. Никакой корреляции с тем, что MyProc обязательно должна что-то обнилить - я не вижу.

Какая вообще разница, каким образом переданы аргументы функции? А если функция имеет прототип:

MyProc(var a: Ta);

в данном случае почему-то нету проблем с финализацией переменной "a"? Хотя в данном случае никакого обнуления по-умолчанию в обработчике MyProc не будет.
Так что фраза - непонятна.


 
Rouse_ ©   (2013-06-12 01:31) [19]


> Нелогичность я описал - допустим, переменная типа "string"
> - почему-то зануляется (то есть, в применении к строке -
>  становится равной пусто). А вот переменная типа integer
> - почему то не зануляется.

Не зануляется а финализируется, как и любой динамический блок данных, за корректную работу с которым отвечает менеджер памяти.
А переменная типа integer, не нуждается в этом.

В частности ты высказал следующее предположение:

> По примеру "const" - это ведь передача как "var",

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

С VAR и OUT параметрами механизм несколько иной.


 
Rouse_ ©   (2013-06-12 01:33) [20]


> в чем проблема?
>
> Пытаюсь смотреть с точки зрения компилятора. Ок, мы имеем
> вызов:

Для того чтобы смотреть с точки зрения компилятора - достаточно открыть CPU-View и посмотреть на то, как оно выглядит на самом деле.


 
EuSet   (2013-06-12 01:42) [21]


> указание директивы const как минимум является командой компилятору
> не генерировать в прологе и эпилоге функции код инициализации
> и финализации блока данных (строки)

хорошо, но:

> С VAR ... параметрами механизм несколько иной.

разве для передачи параметра по VAR - существует код инициализации и финализации блока данных?!


 
Rouse_ ©   (2013-06-12 01:45) [22]


> разве для передачи параметра по VAR - существует код инициализации
> и финализации блока данных?!

конечно


 
Германн ©   (2013-06-12 01:47) [23]

Имхо вы все (ну почти все) запутались с понятиями "инициализация" и "финализация".


 
Rouse_ ©   (2013-06-12 01:48) [24]


> Германн ©   (12.06.13 01:47) [23]
> Имхо вы все (ну почти все) запутались с понятиями "инициализация"
> и "финализация".

Странно, как в них запутаться-то можно? :)


 
Rouse_ ©   (2013-06-12 02:00) [25]


> EuSet   (12.06.13 01:42) [21]


Разбери подробно вот этот код под отладчиком, тут все как на ладони тебе будет.
И сравнение VAR с CONST и где инициализация с финализацией:

procedure Foo1(const V: string);
begin
 ShowMessage(V);
end;

procedure Foo2(var V: string);
begin
 ShowMessage(V);
end;

procedure Foo2Ex(var V: string);
begin
 V := V + "1";
 ShowMessage(V);
end;

procedure Foo3(out V: string);
begin
 V := "3";
 ShowMessage(V);
end;

procedure TForm1.FormCreate(Sender: TObject);
var
 S: string;
begin
 S := "1";
 Foo1(S);
 Foo2(S);
 Foo2Ex(S);
 Foo3(S);
end;


 
NoUser ©   (2013-06-12 03:02) [26]

>  Что это за логика?!
А что за логика - кочан в выхлопную трубу? (out, значит, out)


 
Германн ©   (2013-06-12 04:17) [27]


> Rouse_ ©   (12.06.13 01:48) [24]
>
>
> > Германн ©   (12.06.13 01:47) [23]
> > Имхо вы все (ну почти все) запутались с понятиями "инициализация"
> > и "финализация".
>
> Странно, как в них запутаться-то можно? :)

Наверно как-то можно. Раз пока все (или почти все) говорят про финализацию.
А про инициализацию молчат.


 
MBo ©   (2013-06-12 06:15) [28]

>но при этом на классы опять же не распространяется?
Перечисленные типы - управляемые,  управление памятью автоматическое, а для классов - ручное.


 
Sha ©   (2013-06-12 09:47) [29]

Океюшки. Попробую еще раз.

Строка перед вызовом может иметь какое-то значение.
Но его передавать нельзя, т.к. тогда от него может зависеть значение после вызова, что противоречит смыслу out-параметра.
Передавать мусор тоже нельзя, т.к. его невозможно финализировать после возврата.
Остается одна единственная возможность - передавать пустую строку.
Поэтому параметр инициализируется пустым значением при помощи финализации старого значения )


 
RWolf ©   (2013-06-12 11:36) [30]


>  его передавать нельзя, т.к. тогда от него может зависеть
> значение после вызова, что противоречит смыслу out-параметра.
>


а неизменное значение Integer-аргумента после возврата не противоречит смыслу out-параметра?

я до сего времени вообще рассматривал out просто как декларацию намерений программиста.


 
Sha ©   (2013-06-12 11:59) [31]

Может это и не совсем нормально.
Нам нужно вернуть отсутствие значения.
Что-либо удобоваримое для integer-параметра трудно придумать.
Наверно, поэтому возвращается грязь (старое значение).


 
EuSet   (2013-06-12 13:54) [32]


> Строка перед вызовом может иметь какое-то значение.
> Но его передавать нельзя, т.к. тогда от него может зависеть
> значение после вызова, что противоречит смыслу out-параметра.

ок, эта мысль вполне ясна


> Передавать мусор тоже нельзя, т.к. его невозможно финализировать
> после возврата.

да почему нельзя то?! А вот если параметр передают как "VAR" то почему-то можно и мусор передавать и никаких проблем с финализацией не происходит?

Тип "string" имеет вполне понятную и знакомую компилятору структуру. Вот передал ты в качестве "out" параметра строку - она любой может быть - пустой, содержащей бессмысленный набор букв, но сама строка то валидная, её структура известна. Тело функции может изменить строку, может вообще не менять "out" параметр. Независимо от этого вызывающий код легко может финализировать строку. Ну передавал он s="один вариант", а в ответ получил s="нечто иное" - какая разница? Строка прекрасно финализируется. Точно также как передать её по "VAR" и тело функции изменит строку.

В чем проблема то? Не понимаю.


 
Sha ©   (2013-06-12 14:35) [33]

Строка есть указатель на некую структуру данных, хранящую длину, счетчик ссылок и др.
Понятно, что передавать указатель в космос некорректно. Это и написано.
Разумеется, можно передать указатель на структуру с согласованными данными. Но в этом случае мы не получим отсутствие данных, как хотелось бы получить для out-параметра.


 
NoUser ©   (2013-06-12 14:35) [34]

> В чем проблема то? Не понимаю
+1 ;)


 
Sha ©   (2013-06-12 15:04) [35]

Вот аналогия.

Когда передаем out-параметр, то тем самым мы передаем пустую коробку из-под ксерокса )
А когда var-параметр, то на дне лежит сотня рублей на пиво.

Так понятно?


 
MBo ©   (2013-06-12 15:06) [36]

>а неизменное значение Integer-аргумента после возврата не противоречит смыслу out-параметра?

Если в функции объявлены локальные параметры, то Integer или объект не инициализируются
"логичными значениями" и не финализируются, в отличие от string (управляемых типов). Те же яйца.


 
Sha ©   (2013-06-12 15:19) [37]

> MBo

Последовательнее было бы вообще запретить передачу объектов как out-параметров.


 
MBo ©   (2013-06-12 15:32) [38]

>Sha ©   (12.06.13 15:19) [37]

А почему? Объекты вроде ничем не лучше или хуже PChar или других указательных типов с ручным управлением.


 
Sha ©   (2013-06-12 15:51) [39]

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


 
EuSet   (2013-06-12 16:41) [40]


> Понятно, что передавать указатель в космос некорректно.
> Это и написано.

а как ты в дельфи сможешь передать строку с указателем в космос? Объявление string автоматически "создает" валидную структуру. Поэтому еще раз - какие проблемы с финализацией?


> можно передать указатель на структуру с согласованными данными.
>  Но в этом случае мы не получим отсутствие данных, как хотелось
> бы получить для out-параметра.

так мы и в текущей реализации не получаем отсутствие данных. Переменная out типа string вполне себе на входе функции является строкой. Пустой, валидной строкой.

Фактически, всем переданным переменным типа "string" как "out"-параметр считай перед исполнением тела функции делается s := ""


> Когда передаем out-параметр, то тем самым мы передаем пустую
> коробку из-под ксерокса )
> А когда var-параметр, то на дне лежит сотня рублей на пиво.

это ты описал как бы хорошо, если бы так было. Реально же без ухищрений в дельфи нельзя объявить переменную типа string с невалидной структурой. Она автоматом просто инициализируется. Поэтому вызывающий код передает вполне конкретную строку. Почему в случае с "out" эта строка обязательно должна быть переназначена на пустую строку - я не понял.
С учетом того, что для числовых значений этого, например, не происходит. Если строка приводится к дефолтному значению для строк (пустая строка), почему Integer не приводится к дефолтному значению (ноль)?



Страницы: 1 2 вся ветка

Форум: "Прочее";
Текущий архив: 2013.12.08;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.58 MB
Время: 0.003 c
2-1361811947
ttt
2013-02-25 21:05
2013.12.08
Обрезать строку


2-1361414339
ixen
2013-02-21 06:38
2013.12.08
dataset не фильтрует время


15-1371536120
Dimka Maslov
2013-06-18 10:15
2013.12.08
Почему же так


15-1369665361
Rouse_
2013-05-27 18:36
2013.12.08
Пакет статей по перехвату.


2-1361469987
shup
2013-02-21 22:06
2013.12.08
матрица инциденции Delphi





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский