Delphi: Comment éviter underflow EIntOverflow quand la soustraction?
-
19-09-2019 - |
Question
Microsoft indique, dans la documentation GetTickCount, que vous ne pourriez jamais comparer tick compte pour vérifier si un intervalle est passé. par exemple:.
Mauvaise (pseudo-code):
DWORD endTime = GetTickCount + 10000; //10 s from now
...
if (GetTickCount > endTime)
break;
Le code ci-dessus est mauvais parce qu'il est suceptable de renversement du compteur de la tique. Par exemple, supposons que l'horloge est à la fin de sa gamme:
endTime = 0xfffffe00 + 10000
= 0x00002510; //9,488 decimal
Ensuite, vous effectuez votre chèque:
if (GetTickCount > endTime)
Ce qui est satisfait immédiatement, puisque GetTickCount
est plus grand que endTime
:
if (0xfffffe01 > 0x00002510)
La solution
vous devriez toujours lieu soustraire les deux intervalles de temps:
DWORD startTime = GetTickCount;
...
if (GetTickCount - startTime) > 10000 //if it's been 10 seconds
break;
En regardant le même calcul:
if (GetTickCount - startTime) > 10000
if (0xfffffe01 - 0xfffffe00) > 10000
if (1 > 10000)
Ce qui est très bien en C / C ++, où le compilateur se comporte une certaine manière.
Mais qu'en est-Delphi?
Mais quand j'effectuer les mêmes mathématiques en Delphi, avec trop-plein de vérifier sur ({Q+}
, {$OVERFLOWCHECKS ON}
), la soustraction des deux chefs d'accusation de tiques génère une exception EIntOverflow lorsque la TickCount roule sur:
if (0x00000100 - 0xffffff00) > 10000
0x00000100 - 0xffffff00 = 0x00000200
Quelle est la solution destinée à ce problème?
Edit: j'ai essayé de désactiver temporairement OVERFLOWCHECKS
:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
Mais la soustraction jette encore une exception EIntOverflow
.
Y at-il une meilleure solution, impliquant des moulages et des types de variables intermédiaires plus grandes?
Mise à jour
Une autre question SO j'ai demandé a expliqué pourquoi {$OVERFLOWCHECKS}
ne fonctionne pas. Il fonctionne apparemment seulement à la Fonction niveau, pas la ligne niveau. Ainsi, alors que les ne pas travail:
{$OVERFLOWCHECKS OFF}]
delta = GetTickCount - startTime;
{$OVERFLOWCHECKS ON}
les fait travail:
delta := Subtract(GetTickCount, startTime);
{$OVERFLOWCHECKS OFF}]
function Subtract(const B, A: DWORD): DWORD;
begin
Result := (B - A);
end;
{$OVERFLOWCHECKS ON}
La solution
Que diriez-vous d'une fonction simple comme celui-ci?
function GetElapsedTime(LastTick : Cardinal) : Cardinal;
var CurrentTick : Cardinal;
begin
CurrentTick := GetTickCount;
if CurrentTick >= LastTick then
Result := CurrentTick - LastTick
else
Result := (High(Cardinal) - LastTick) + CurrentTick;
end;
Vous avez donc
StartTime := GetTickCount
...
if GetElapsedTime(StartTime) > 10000 then
...
Il fonctionnera aussi longtemps que le temps entre StartTime et la GetTickCount actuelle est inférieure à la tristement célèbre 49,7 jours gamme de GetTickCount.
Autres conseils
Je l'ai cessé de faire ces calculs partout après avoir écrit quelques fonctions d'aide qui sont appelés à la place.
Pour utiliser le nouveau GetTickCount64()
fonction sur Vista et plus tard, il est le nouveau type:
type
TSystemTicks = type int64;
qui est utilisé pour tous ces calculs. GetTickCount()
est jamais appelé directement, la fonction d'aide GetSystemTicks()
est utilisée à la place:
type
TGetTickCount64 = function: int64; stdcall;
var
pGetTickCount64: TGetTickCount64;
procedure LoadGetTickCount64;
var
DllHandle: HMODULE;
begin
DllHandle := LoadLibrary('kernel32.dll');
if DllHandle <> 0 then
pGetTickCount64 := GetProcAddress(DllHandle, 'GetTickCount64');
end;
function GetSystemTicks: TSystemTicks;
begin
if Assigned(pGetTickCount64) then
Result := pGetTickCount64
else
Result := GetTickCount;
end;
// ...
initialization
LoadGetTickCount64;
end.
Vous pouvez même suivre manuellement la pellicule autour de la valeur de retour de GetTickCount()
et renvoyer un nombre tick vrai système 64 bits sur les systèmes antérieurs aussi, ce qui devrait fonctionner assez bien si vous appelez la fonction GetSystemTicks()
au moins tous les quelques jours. [ Je crois me souvenir d'une mise en œuvre de ce quelque part, mais ne me souviens pas où il était. Gabr posté un lien et la de mise en œuvre.]
Maintenant, il est trivial de mettre en œuvre des fonctions telles que
function GetTicksRemaining(...): TSystemTicks;
function GetElapsedTicks(...): TSystemTicks;
function IsTimeRunning(...): boolean;
qui va masquer les détails. L'appel de ces fonctions au lieu de calculer des durées en place sert également de la documentation de l'intention de code, donc moins de commentaires sont nécessaires.
Modifier
Vous écrivez dans un commentaire:
Mais comme vous l'avez dit, les solutions de repli sur Windows 2000 et XP GetTickCount laisse encore le problème d'origine.
Vous pouvez corriger cela facilement. D'abord, vous n'êtes pas besoin pour revenir à GetTickCount()
- vous pouvez utiliser le code fourni Gabr de calculer un 64 bits tick compter sur les anciens systèmes ainsi. (Vous pouvez remplacer timeGetTime()
avec GetTickCount)
si vous voulez.)
Mais si vous ne voulez pas faire que vous pouvez tout aussi bien désactiver les contrôles de portée et de débordement dans les fonctions d'aide, ou vérifier si le diminuende est plus petit que le soustractif et corriger cela en ajoutant 100.000.000 $ (2 ^ 32) pour simuler un nombre de graduation 64 bits. Ou mettre en œuvre les fonctions en assembleur, auquel cas le code n'a pas les chèques (pas que je conseillerais, mais il est possible).
Vous pouvez également utiliser DSiTimeGetTime64 de la DSiWin32 :
threadvar
GLastTimeGetTime: DWORD;
GTimeGetTimeBase: int64;
function DSiTimeGetTime64: int64;
begin
Result := timeGetTime;
if Result < GLastTimeGetTime then
GTimeGetTimeBase := GTimeGetTimeBase + $100000000;
GLastTimeGetTime := Result;
Result := Result + GTimeGetTimeBase;
end; { DSiTimeGetTime64 }
Vous pouvez utiliser le type de données pour éviter tout débordement Int64:
var
Start, Delta : Int64;
begin
Start := GetTickCount;
...
Delta := GetTickCount - start;
if (Delta > 10000) then
...