Scilab et le caractère NUL (0) dans les chaines de caractères

Hier, j’ai passé un bon moment à lutter avec Scilab et la lecture de données dans un fichier binaire. Au final, le problème venait de la présence du caractère ASCII NUL (valeur 0) dans la chaine de caractères que je lisais avec mgetstr.

Le problème présenté ici affecte la plupart des fonctions concernées : strindex, strcmp, strsubst, part, strtok

Le problème

Utilisons la fonction strindex pour chercher l’indice du début d’un motif « def » dans une chaine de caractères « abcdef » :

str = "abcdef"

idx = strindex(str, "def")

Renvoi :

    4.

Ajoutons un espace, dont la valeur ASCII est 32, au début de la chaine précédente :

str = ascii(32) + str        // Ajout du caractère espace

idx = strindex(str, "def")

Renvoi :

    5.

Ajoutons une tabulation, dont la valeur ASCII est 8, au début de la chaine précédente :

str = ascii(8) + str        // Ajout du caractère tabulation

idx = strindex(str, "def")

Renvoi :

    6.

Aucun problème donc jusqu’ici.

Ajoutons maintenant le caractère NUL, dont la valeur ASCII est 0, au début de la chaine précédente :

str = ascii(0) + str        // Ajout du caractère NUL

idx = strindex(str, "def")

Renvoi :

     []

Aïe ! Aïe ! Aïe !

Le problème est identique avec d’autres fonctions :

part(str, 1)

 ans  =
 
     
 
part(str, 7)

 ans  =
 
     
 
part(str, 8)

 ans  =

L’explication

Les fonctions de Scilab qui traitent les chaines de caractères sont écrites en C. Or pour stocker une chaine de n caractères, il faut (au minimum) un tableau de n+1 éléments en langage C. Le dernier élément identifie la fin de la chaine de caractères. Et bien entendu, le caractère qui a été retenu pour remplir cette mission est le caractère NUL dont la valeur ASCII est 0 (voir la FAQ C à ce sujet).

Conséquence : si une chaine contient le caractère NUL (0), tous les caractères qui suivent seront ignorés.

Par exemple :

str = "hello world"

substr(1) = part(str, 1:5)
substr(2) = part(str, 7:$)

Renvoi :

!hello  !
!       !
!world  !

Alors que :

str = "hello" + ascii(0) + "world"

substr(1) = part(str, 1:5)
substr(2) = part(str, 7:$)

Renvoi :

!hello  !
!       !
!       !

Une solution

La solution consiste à remplacer le caractère NUL par un autre avant de traiter la chaine de caractères. Mais attention, il faudra impérativement remplacer la valeur numérique du caractère 0 par une autre (de 1 à 255) car nous avons vu précédemment que les fonctions Scilab n’avaient aucun effet.

str = "hello" + ascii(0) + "world"

 str  =
 
 hello world  
 
ascii(str)

 ans  =
 
    104.    101.    108.    108.    111.    0.    119.    111.    114.    108.    100.  
 
newStr = strsubst(str, 0, 32)
                              !--error 999
strsubst : Type erroné de l'argument d'entrée n°3 : Une chaîne de caractères attendue.
 
 
newStr = strsubst(str, ascii(0), ascii(32))

 newStr  =
 
 hello

Il faut donc faire la conversion implicitement :

numStr = ascii(str)

 numStr  =
 
    104.    101.    108.    108.    111.    0.    119.    111.    114.    108.    100.  
 
idx = numStr==0

 idx  =
 
  F F F F F T F F F F F  
 
numStr(idx) = 32 // par exemple

 numStr  =
 
    104.    101.    108.    108.    111.    32.    119.    111.    114.    108.    100.  
 
newStr = ascii(numStr)

 newStr  =
 
 hello world

Voila on peut maintenant gérer la chaine de caractères avec les fonctions Scilab :

substr(1) = part(newStr, 1:5)

 substr  =
 
!hello  !
!       !
!       !
 
substr(2) = part(newStr, 7:$)

 substr  =
 
!hello  !
!       !
!world  !
 
idx = strindex(newStr, "world")

 idx  =
 
    7.

Pourquoi mettre le caractère NUL dans une chaine de caractères ?

Quel est l’intérêt de mettre un caractère NUL dans une chaine ? J’aimerais pouvoir vous répondre : « aucun ». Mais j’ai mentionné la lecture de fichier tout au début de ce billet. Un fichier binaire peut contenir des valeurs numériques et des chaines de caractères.

Prenons l’exemple d’un fichier binaire contenant une valeur 0 suivie d’une chaine de caractères :

fd = mopen("test.dat", "wb")
mput(0, "uc", fd)
mput(ascii("hello world"), "uc", fd)
mclose(fd)

La lecture ne pose pas de problème particulier :

fd = mopen("test.dat", "rb")
str = mgetstr(12, fd)
mclose(fd)

Ce qui donne bien :

str

 str  =
 
  hello world  
 
ascii(str)

 ans  =
 
    0.    104.    101.    108.    108.    111.    32.    119.    111.    114.    108.    100.

Par contre :

idx = strindex(str, "world")

 idx  =
 
     []

Vous n’êtes pas convaincu par cet exemple trop simpliste, passons alors à un exemple pratique.

Validation de fichier DICOM

Les fichiers DICOM sont des fichiers binaires utilisés pour le stockage d’informations médicales. Ils contiennent des valeurs numériques et des chaines de caractères. Avant de lire un de ces fichiers, il est utile de tester sa validité. Le test le plus robuste consiste à chercher la chaine de caractères « 1.2.840.10008. » dans les premiers octets du fichier. La position de la chaine dans le fichier est inconnue et rien n’empêche que des valeurs nulles soient écrites avant…

La mauvaise solution :

fd = mopen("test.dcm", "rb")

buffer = mgetstr(2000, fd)
chk = strindex(buffer, "1.2.840.10008.")

if isempty(chk) then
    disp("Fichier non valide")
else
    disp("Fichier valide")
end

mclose(fd)

Le code ci-dessus renverra constamment « Fichier non valide » même si la chaine « 1.2.840.10008. » est bien présente dans le fichier.

La solution plus robuste à utiliser :

fd = mopen("test.dcm", "rb")

buffer = mgeti(2000, "uc", fd)
idx = buffer~=0
chk = strindex(ascii(buffer(idx)), "1.2.840.10008.")

if isempty(chk) then
    disp("Fichier non valide")
else
    disp("Fichier valide")
end

mclose(fd)

Dans cette version du code, les caractères NUL seront pris en compte avant de chercher la chaine « 1.2.840.10008. » Ce code fonctionne donc comme il faut.

Sortie du livre « Accelerating MATLAB Performance »

Le nouveau livre de Yair Altman (Undocumented MATLAB), vient de paraître :

Couverture du livre Accelerating MATLAB Performance

Au sommaire :

  • CHAPTER 1: Introduction to Performance Tuning
  • CHAPTER 2: Profiling MATLAB Performance
  • CHAPTER 3: Standard Performance-Tuning Techniques
  • CHAPTER 4: MATLAB-Specific Techniques
  • CHAPTER 5: Implicit Parallelization (Vectorization and Indexing)
  • CHAPTER 6: Explicit Parallelization Using MathWorks Toolboxes
  • CHAPTER 7: Explicit Parallelization by Other Means
  • CHAPTER 8: Using Compiled Code
  • CHAPTER 9: Memory-Related Techniques
  • CHAPTER 10: Graphics and GUI
  • CHAPTER 11: I/O Techniques

D’après l’auteur :

MATLAB est souvent perçu comme une plate-forme appropriée pour le prototypage et la modélisation, mais pas pour les applications « sérieuses ». L’un des principaux reproches est que MATLAB est tout simplement trop lent.

« Accelerating MATLAB Performance » vise à corriger cette perception, en décrivant de multiples façons d’améliorer grandement la vitesse des programmes MATLAB.

Plus d’informations : Accelerating MATLAB Performance book

J’espère pouvoir bientôt lire cet ouvrage. Je ne manquerai pas de publier une critique sur la page Livres de la rubrique MATLAB.

Pour mémoire, Yair Altman est également l’auteur du livre Undocumented Secrets of MATLAB-Java Programming

Installer GNU Octave 3.8.1 sur Linux Xubuntu 14.10

Depuis la version 3.8, GNU Octave dispose d’un environnement de développement (EDI). Cet outil est pour le moment encore en version de test. Il n’est pas activé par défaut lors de l’installation. Il le sera avec la sortie de la version 4 de GNU Octave.

J’avais précédemment montré comment installer GNU Octave sur Windows. Ce billet vous montrera comment l’installer sur Xubuntu 14.10.

GNU Octave Logithèque Ubuntu

Logithèque Ubuntu

Comme pour toutes les distributions Debian, il est possible d’installer GNU Octave à partir du gestionnaire de paquets (apt-get install octave par exemple).

Vous pouvez aussi plus simplement utiliser le menu  » Logithèque Ubuntu  » :
Logithèque Ubuntu

Le paquet octave 3.8.1-1ubuntu1 contient la dernière version à jour de GNU Octave.

GNU Octave et MenuLibre

Une fois installé, le lanceur se trouve normalement dans le menu  » Éducation  » de MenuLibre. Pour ma part, je préfère l’avoir dans le menu Développement. Pour ce faire :

  • ouvrir le menu  » Paramètres > Éditeur de menus « 
  • sélectionner GNU Octave
  • utiliser les flèches en bas à gauche pour déplacer le menu

Gnu Octave MenuLibre

Activation de l’EDI

L’environnement de développement de GNU Octave est encore en phase de test. Il n’est donc pas activé par défaut.

Si vous exécutez GNU Octave, vous obtenez la console classique :
GNU Octave console

Pour forcer GNU Octave à démarrer en mode EDI, il faut ajouter --force-gui via l’éditeur de menu :
GNU Octave MenuLibre

Vous pouvez aussi en profiter pour modifier le dossier de démarrage de GNU Octave en renseignant le champs  » Répertoire de travail « .

L’environnement de développement de GNU Octave devrait maintenant s’ouvrir :
GNU Octave IDE - EDI