ENCORE UN TRIM()

cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 - 31 juil. 2007 à 16:39
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019 - 31 juil. 2007 à 23:44
Cette discussion concerne un article du site. Pour la consulter dans son contexte d'origine, cliquez sur le lien ci-dessous.

https://codes-sources.commentcamarche.net/source/43621-encore-un-trim

BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 23:44
ben oui, faut bien le mettre qlq part.

Adaptation manuelle donne:
__declspec(naked) int __fastcall bntrim(char* psrc)
{ // ECX = psrc , RETOURNE NBR DE CHAR RESTANT
__asm {
mov eax, ecx
mov [esp-4], ecx
cmp byte ptr[eax], 32
jne short gEND
Ltrm1:
add ecx, 1
cmp byte ptr[ecx], 32
je short Ltrm1
gEND:
sub eax, 1
Ltrm2:
mov dl, [ecx]
add eax, 1
add ecx, 1
mov [eax], dl
test dl, dl
jnz short Ltrm2
mov ecx, [esp-4]
cmp eax, ecx
jna short Ltrm4
Ltrm3:
sub eax, 1
cmp byte ptr[eax], 32
je short Ltrm3
add eax, 1
Ltrm4:
mov byte ptr[eax], 0
sub eax, ecx
ret 0
}
}

c'est très bon.
Je préfère le nbr de char restant dans le cas d'un trim, me sert beaucoup plus dans mon taf.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 23:35
Ah ok. J'avais pas vue que le compilo se servait de esi pour faire une "sauvegarde" de ecx. En gros, c'est le

char *d = str;

du code C.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 23:03
normal, il l'a pushé au début et le POP avant de sortir.
Tu ne pourras pas empêcher cela sans faire l'asm manuel.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 22:53
Ah non zut! Il utilise encore esi...
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 22:52
je le cotoie depuis si longtemps...
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 22:51
Bingo!!!

char * __fastcall trim (char *str)
{
char *c str, *d str;

while(*str == ' ') str++;
while(*str) *c++ = *str++;
if(c != d) {
while(*--c == ' ');
c++;
}

*c = 0;

return c;
}

cmp BYTE PTR [ecx], 32 ; 00000020H
push esi
mov eax, ecx
mov esi, ecx
jne SHORT $LN11@
npad 6

$LL7@:
add ecx, 1
cmp BYTE PTR [ecx], 32 ; 00000020H
je SHORT $LL7@

$LN11@:
mov dl, BYTE PTR [ecx]
test dl, dl
je SHORT $LN4@
npad 2
$LL5@:
add ecx, 1
mov BYTE PTR [eax], dl
mov dl, BYTE PTR [ecx]
add eax, 1
test dl, dl
jne SHORT $LL5@

$LN4@:
cmp eax, esi
pop esi
je SHORT $LN15@

$LL2@:
sub eax, 1
cmp BYTE PTR [eax], 32 ; 00000020H
je SHORT $LL2@
add eax, 1

$LN15@:
mov BYTE PTR [eax], 0
ret 0

Il génère bien un push.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 22:43
pas grave, facile à voir ce que serait dans un gros proj avec différents contextes d'appels:

__declspec(naked) char* __fastcall trim(char* psrc)
{
__asm {
cmp BYTE PTR[ecx], 32
mov eax, ecx
mov [esp-4], ecx
jne SHORT $LN11@trim
...
...
$LL5@trim:
add ecx, 1
mov BYTE PTR [eax], dl
mov dl, BYTE PTR [ecx]
add eax, 1
test dl, dl
jne SHORT $LL5@trim
cmp eax, [esp-4]
je SHORT $LN16@trim
$LL2@trim:
...

malheureusement c'est ce qu'on fait à la main, le compilo mettra un PUSH au lieu du [esp-4], il ne se permet jamais ce genre de fantaisie.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 22:33
Je n'y arrive pas. À tout les coups, il me remet le cmp [esi], 32.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 22:12
cmp BYTE PTR [esi], 32 ; 00000020H

ça ne t'alarme pas cette entrée ???
le compilo a inséré la fonction dans l'arbre d'optimisation (ceci dit c'est excellent) mais faut lui faire différents appels pour avoir la fonction bien indépendante pour en juger.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 22:07
revoici le listing avec un minimum d'indentation.

cmp BYTE PTR [esi], 32 ; 00000020H
mov eax, esi
mov ecx, esi
jne SHORT $LN11@trim
npad 7

$LL7@trim:
add ecx, 1
cmp BYTE PTR [ecx], 32 ; 00000020H
je SHORT $LL7@trim

$LN11@trim:
mov dl, BYTE PTR [ecx]
test dl, dl
je SHORT $LN16@trim
npad 2

$LL5@trim:
add ecx, 1
mov BYTE PTR [eax], dl
mov dl, BYTE PTR [ecx]
add eax, 1
test dl, dl
jne SHORT $LL5@trim
cmp eax, esi
je SHORT $LN16@trim

$LL2@trim:
sub eax, 1
cmp BYTE PTR [eax], 32 ; 00000020H
je SHORT $LL2@trim
add eax, 1

$LN16@trim:
mov BYTE PTR [eax], 0
ret 0
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 22:04
release avec optimisation en faveur de la vitesse:

cmp BYTE PTR [esi], 32 ; 00000020H
mov eax, esi
mov ecx, esi
jne SHORT $LN11@trim
npad 7
$LL7@trim:
add ecx, 1
cmp BYTE PTR [ecx], 32 ; 00000020H
je SHORT $LL7@trim
$LN11@trim:

mov dl, BYTE PTR [ecx]
test dl, dl
je SHORT $LN16@trim
npad 2
$LL5@trim:
add ecx, 1
mov BYTE PTR [eax], dl
mov dl, BYTE PTR [ecx]
add eax, 1
test dl, dl
jne SHORT $LL5@trim

cmp eax, esi
je SHORT $LN16@trim
$LL2@trim:

sub eax, 1
cmp BYTE PTR [eax], 32 ; 00000020H
je SHORT $LL2@trim

add eax, 1
$LN16@trim:

mov BYTE PTR [eax], 0

ret 0
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 21:58
on dirait bien.
vérifie ce que produit le compilo.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 21:49
Arff. Oui c'est vrai..
J'ai décortiqué ta fonction et voici le résultat:

char *trim (char *str)
{
char *c str, *d str;

while(*d == ' ') d++;
while(*d) *c++ = *d++;
if(c != str) {
while(*--c == ' ');
c++;
}

*c = 0;

return c; // Retour sur 0 de fin de chaine
}

C'est bon ça ?
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 21:08
Pour ta derniere remarque, bien entendu faut pas y aller comme un sauvage, essaie ma func avec chaine vide, tu verras que n(ira pas hors plage.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 21:07
he he, faut pas regarder le listing C pour juger des perfs.

Tu fais des trim en rafale sur des champs de BDD, 255 char par champ text, si on a 20 blancs au début (deja bien)
avec: if(*c++ !t) d c;
tu vas générer encore 235 sauts internes à la boucle, c'est mortel pour les perfs.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 21:05
On évite aussi de reculer en dehors de la zone mémoire comme pour cette chaine:

char test[] = " ";

Même si les probabilités sont faible, il se pourrait que test-1 soit un espace.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 21:03
Ben, c'est justement pour ne pas reculer que j'ai ajouté
if(*c++ !t) d c;
Suffis simplement de faire *d = 0 et hop c'est fait:

char* trimc(char *str, char t)
{
char *c str, *d str;

while(*str == t) str++;
while(*c = *str++)
if(*c++ !t) d c;
*d = 0; // Ici, d devrais pointer sur la fin+1 de la chaine valide, donc sur un espace

return d; // Retour sur 0 de fin de chaine
}

C'est pas mieux comme ça ?
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 20:53
je ne saisis pas bien l'affaire:

while(*str == t) str++; ici ok
ensuite: while(*c = *str++) c++; suffit pour copie
ne reste plus qu'à reculer *c tant que == t, avant de lui mettre un 0.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:50
Oups, c'est un lea et non un mov.
J'avais testé avec un malloc et il stockait sa valeur de retour dans esi. Ça générait donc mov ecx, esi.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:46
C'est en release avec optimisation pour la vitesse.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:44
BruNews >> Cette adaptation C est-elle correcte ?

char* trimc(char *str, char t)
{
char *c str, *d str;

while(*str == t) str++;
while(*str) {
*c = *str++;
if(*c++ !t) d c;
}
*d = 0;

return d; // Retour sur 0 de fin de chaine
}

Aussi, voici le listing de la fonction main:
sub esp, 16 ; 00000010H

mov eax, DWORD PTR ??_C@_0N@IIHOOOEL@?5?5Bonjour?5?5?5?$AA@
mov ecx, DWORD PTR ??_C@_0N@IIHOOOEL@?5?5Bonjour?5?5?5?$AA@+4
mov edx, DWORD PTR ??_C@_0N@IIHOOOEL@?5?5Bonjour?5?5?5?$AA@+8
mov DWORD PTR _test$[esp+16], eax
mov al, BYTE PTR ??_C@_0N@IIHOOOEL@?5?5Bonjour?5?5?5?$AA@+12
mov DWORD PTR _test$[esp+20], ecx

lea ecx, DWORD PTR _test$[esp+16]
mov DWORD PTR _test$[esp+24], edx
mov BYTE PTR _test$[esp+28], al
call @bntrim@4

lea ecx, DWORD PTR _test$[esp+16]
push ecx
push OFFSET ??_C@_06OJMELMDJ@?$CFs?9fin?$AA@
call _printf

xor eax, eax
add esp, 24 ; 00000018H

ret 0
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:34
Sur ce code:

int main (void)
{
char test[] = " Bonjour ";

bntrim(test);
printf("%s-fin", test);

return 0;
}

Fonctionne bien.

Génère bien un mov ecx, xxx à l'appel.
Je préfère moi aussi le mov au push. Pas besoin d'extraire la valeur du stack dans la fonction.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 20:25
Pour ce qui est de la fonction de la source, il est clair que c'est un exxemple complet de bug. Dépêchons nous de discuter ici dans les comments, je sens que l'ensemble disparaitra sous peu.
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 20:23
Essaie fastcall en C et tu nous diras, pas testé.
Je préfère de loin MOV à PUSH.

Retourner ce qu'on passe à la fonction, aucun intérêt d'après moi puisqu'on l'a déjà.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:18
vicenzo >> Ah ok. J'me disais aussi.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 20:16
héhéhé.
Ben c'est sûr qu'en __fastcall ...

Mais il y a tout de même un mov ecx, xxx :P
cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 1
31 juil. 2007 à 20:13
ok pour le retour sur fin de chaine mais je trouve que lors d'un trim il est beaucoup plus utile de retourner le pointeur intial afin de pourvoir utliser la valeur de retour pour un strcpy, etc... De pus cela reste plus cohérent avec le reste de la lib C...

Pour la modif du pointeur interne, je parlai de la fonction Trim() de darkpoulpo de cette source. Il passe en param un char** et modifie sa valeur dans la fonction. Donc si le char * est alloué par malloc et que l'on utilise ce char* avec le Trim de darkpoulpo en passant son adresse et que ensuite on désallloue le char * : Bingo : erreur !
BruNews Messages postés 21040 Date d'inscription jeudi 23 janvier 2003 Statut Modérateur Dernière intervention 21 août 2019
31 juil. 2007 à 20:10
Quelle prise de tête pour un banal trim.
Je vous ai fourni une version sans un seul push, que soit à l'appel ou en interne, ne reste qu'à l'utiliser.
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 19:36
Le retour sur le 0 de fin de chaine est voulu. Permet un chainage direct si, par exemple, on fait un strcat après. Permet aussi d'avoir le nombre de char présent dans la chaine:

char *c = " Bonjour ";
int i = trimc(c, ' ')-c;

À utiliser comme bon te semblera.

Pour ce qui est de modifier le pointeur en interne, je n'est jamais eu de problème avec ça.
J'ai essayer ceci juste au cas où:
char *test = (char*)malloc(255);

strcpy(test, " bonjour ");
trimc(test, ' ');
printf("%s\n", test);
free(test);

et tout fonctionne parfaitement. test pointe toujours au même endroit du début à la fin.

J'ai jeté un coup d'oeil au listing. Voici un abrégé de ce qui ce passe (fonction en __stdcall):

// Appel de la fonction
// On met les valeurs sur le stack
push 32 // c'est l'espace
push ecx // dison qu'ecx contient l'adresse du pointeur
call trimc

//dans trimc
mov ebx, [ebp] // on met le contenue de ebp dans ebx
// On travaillera donc sur ebx et sur le stack

En gros, on travail sur une copie du pointeur qui est sur le stack et non le pointeur lui-même. L'original ne devrait donc pas être altéré. ecx devrait donc toujours pointer au bon endroit après l'appel.
Va voir le listing d'un code, je crois que ça t'expliquera bien mieux que moi.

J'espère que je n'est pas dis de conneries.
cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 1
31 juil. 2007 à 19:06
La motivation de mon commentaire initial (hormis faire refroidir mon café trop chaud) était de mettre en évidence le fait que la fonction retournait un pointeur différent de celui fournit (ce qui n'est pas robuste et source de bug) et le fait que de modifier le pointeur en interne dans la fonction rend sa délallocation pontentiellement suicidaire
cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 1
31 juil. 2007 à 19:00
Salut SAKindom,

pour le rtrim, ok le café avait déja perdu 1°C - j'étais pressé de le déguster.

on trimmme à gauche, à droite, ou des deux, j'avais donc deux primitives rassemblées pour en faire un troisieme..
Effectivement cela toujours plus optimisé sans les deux sous appels

Sinon la valeur de retour n'est pas bonne car tu utilise c dans le code est donc tu ne retournes pas la valeur de départ..

Au final voila le code pour DarkPoulpos :

char* trimc(char *str, char t)
{
char *s1=str, *s2=str;

while(*s2 == t) s2++;
while(*s2) *s1++ = *s2++;
while (*--s1 == t);
*++s1 = 0;

return str;
}
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 18:42
Voici quelque chose de mieux je pense:

char* trimc(char *str, char t)
{
char *c str, *d str;

while(*str == t) str++;
while(*str) {
*c = *str++;
if(*c++ !t) d c;
}
*d = 0;

return d; // Retour sur 0 de fin de chaine
}

char* rtrimc(char *str, char t)
{
char *c = str;
while(*str) if(*str++ !t) c str;
*c = 0;

return c; // Retour sur 0 de fin de chaine
}
SAKingdom Messages postés 3212 Date d'inscription lundi 7 novembre 2005 Statut Membre Dernière intervention 16 février 2009 15
31 juil. 2007 à 18:14
Salut vicenzo.
Hmmm, ton rtrimc n'est pas bon. Il remplace tout les espaces par des 0 à partir du début de la chaine. Utilisé après un ltrimc, on ne verra pas la différence mais si on l'utilise seul, adieu la chaine.
Ceci est fonctionnel:

char* rtrimc(char *str, char t)
{
while(*str) str++;
while (*--str == t);
*++str = 0;

return str; // Retour sur 0 de fin de chaine
}

Par la même occation, ton trim se trouve à parcourir 2 fois la chaine. 1 pour les ltrimc est 1 autre fois pour le rtrimc.
Ce trimc est fonctionnel (pas testé en profondeur):

char* trimc(char *str, char t)
{
char *c = str;

while(*str == t) str++;
while(*str) *c++ = *str++;
while (*--c == t);
*++c = 0;

return c; // Retour sur 0 de fin de chaine
}

J'espère que j'ai pas fais trop d'erreurs, car comme de disais, je n'est pas eu le temps de tout tester minutieusement.
cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 1
31 juil. 2007 à 17:14
Bon, pour la forme afin de faire un peu tiedir le café, voici un trim fait à la à volée pour expliciter mon propos.
le code est ecrit à l 'arrache (le café froid, c'est pas bon) et sans aucune contrôle mais il trimme bien avec décalage vers la gauche


#define trim(s) trimc(s, ' ')
#define rtrim(s) rtrimc(s, ' ')
#define ltrim(s) ltrimc(s, ' ')

char* trimc(char* s, char t)
{
rtrimc(s, t);
ltrimc(s, t);

return s;
}

char* rtrimc(char* str, char t)
{
char *s = str;

while (*s)
if (*s++==t)
{
*--s=0;
break;
}

return str;
}


char* ltrimc(char* str, char t)
{
if (*str == t)
{
char *s1=str, *s2=str;

while (*s2 == t) s2++;
while (*s2) *s1++ = *s2++;

*s1 = 0;
}

return str;
}
cs_vicenzo Messages postés 178 Date d'inscription mardi 16 août 2005 Statut Membre Dernière intervention 25 août 2010 1
31 juil. 2007 à 16:39
Cette implémentation est dangeureuse car si ton pointeur avait alloué par un malloc, pour le free c'est foutu, puisque ton pointeur ne point plus sur la même adresse après l'appel.

un trim consiste en un trim gauche et un trim droite. A droite c'est simple, mais à gauche il ne faut pas se contenter de déplacer le pointeur il faut décaler les valeurs afin de garder la même adresse de départ.

Pour une implémentation robuste, toujours retourner le pointeur passé en paramètre car tu n'a aucune idée de la manière dont il a été alloué.
Rejoignez-nous