Outils pour utilisateurs

Outils du site


lang:c:attribut

Les attributs ci-dessous sont non standards et dépendent du compilateur.

__attribute__((const)) et __attribute__((pure))

Il est aussi possible d'utiliser [[gnu::pure]] et [[gnu::const]].

  • Définition

Une fonction const ne modifie aucune donnée et ne lit que ces arguments (pas de variable globale et pas de déréférencement de pointeur, y compris parmi ses arguments).

Une fonction pure ne modifie aucune donnée accessible par d'autres fonctions (généralement appelées des observateurs). Elle peut accéder en lecture seule à des variables globales ou encore déréférencer des pointeurs passés en argument.

  • Conséquence

Les fonctions const sont garanties idempotentes.

Les fonctions pure peuvent ne pas renvoyer la même valeur avec les mêmes arguments. Une variable globale ou bien le contenu d'un tableau passé en pointeur peut changer. Cependant, le compilateur va optimiser en supposant l'application single-thread. Donc si deux fonctions sont appelées avec les mêmes arguments et que le compilateur est sûr que les arguments ne sont pas modifiés (y compris le contenu des pointeurs), il ne va appeler la fonction qu'une seule fois et réutiliser la valeur retour.

The const attribute imposes greater restrictions on a function’s definition than the similar pure attribute.

The const attribute prohibits a function from reading objects that affect its return value between successive invocations.

In general, since a function cannot distinguish data that might change from data that cannot, const functions should never take pointer or, in C++, reference arguments.

Using the GNU Compiler Collection, Archive v9.2 le 06/03/2020

Rappel : l’attribut pure n'est pas thread-safe si la fonction fait appel à une variable globale ou si un de ces arguments peut être modifiée par un autre thread.

GCC optimization of pure functions Archive du 19/07/2017 le 15/07/2020

  • Exemples

Ici, il faut bien activer l'option -O2.

CodeAssembleur
Deux appels consécutifs : optimisation.

cpp/attribute/pure1.cpp
[[gnu::pure]] int square(int x);
 
int main(int argc, char* /*argv*/[])
{
  int x = square(argc);
  int y = square(argc);
  return x + y;
}
cpp/attribute/pure1.cpp.asm
0000000000000000 <main>:
   0:	endbr64 
   4:	sub    rsp,0x8
   8:	call   d <main+0xd>
			9: R_X86_64_PLT32	square(int)-0x4
   d:	add    rsp,0x8
  11:	add    eax,eax
  13:	ret    
Modification entre deux appels : pas d'optimisation.

cpp/attribute/pure2.cpp
[[gnu::pure]] int square(int x);
 
int main(int argc, char* /*argv*/[])
{
  int x = square(argc);
  argc++;
  int y = square(argc);
  return x + y;
}
cpp/attribute/pure2.cpp.asm
0000000000000000 <main>:
   0:	endbr64 
   4:	push   rbp
   5:	push   rbx
   6:	mov    ebx,edi
   8:	sub    rsp,0x8
   c:	call   11 <main+0x11>
			d: R_X86_64_PLT32	square(int)-0x4
  11:	lea    edi,[rbx+0x1]
  14:	mov    ebp,eax
  16:	call   1b <main+0x1b>
			17: R_X86_64_PLT32	square(int)-0x4
  1b:	add    rsp,0x8
  1f:	add    eax,ebp
  21:	pop    rbx
  22:	pop    rbp
  23:	ret    
Utilisation intermédiaire par copie par une fonction : pas d'optimisation !!!

cpp/attribute/pure3.cpp
[[gnu::pure]] int square(int x);
 
void f(int a);
 
int main(int argc, char* /*argv*/[])
{
  int x = square(argc);
  f(argc);
  int y = square(argc);
  return x + y;
}
cpp/attribute/pure3.cpp.asm
0000000000000000 <main>:
   0:	endbr64 
   4:	push   rbp
   5:	mov    ebp,edi
   7:	push   rbx
   8:	sub    rsp,0x8
   c:	call   11 <main+0x11>
			d: R_X86_64_PLT32	square(int)-0x4
  11:	mov    edi,ebp
  13:	mov    ebx,eax
  15:	call   1a <main+0x1a>
			16: R_X86_64_PLT32	f(int)-0x4
  1a:	mov    edi,ebp
  1c:	call   21 <main+0x21>
			1d: R_X86_64_PLT32	square(int)-0x4
  21:	add    rsp,0x8
  25:	add    eax,ebx
  27:	pop    rbx
  28:	pop    rbp
  29:	ret    
Utilisation intermédiaire par copie par une fonction inconnue pure : optimisation.

cpp/attribute/pure4.cpp
[[gnu::pure]] int square(int x);
 
[[gnu::pure]] int f(int a);
 
int main(int argc, char* /*argv*/[])
{
  int x = square(argc);
  x = x + f(argc);
  int y = square(argc);
  return x + y;
}
cpp/attribute/pure4.cpp.asm
0000000000000000 <main>:
   0:	endbr64 
   4:	push   rbp
   5:	mov    ebp,edi
   7:	push   rbx
   8:	sub    rsp,0x8
   c:	call   11 <main+0x11>
			d: R_X86_64_PLT32	square(int)-0x4
  11:	mov    edi,ebp
  13:	mov    ebx,eax
  15:	call   1a <main+0x1a>
			16: R_X86_64_PLT32	f(int)-0x4
  1a:	add    rsp,0x8
  1e:	lea    eax,[rax+rbx*2]
  21:	pop    rbx
  22:	pop    rbp
  23:	ret    
Utilisation en lecture au milieu : optimisation.

cpp/attribute/pure5.cpp
#include <cstdlib>
 
[[gnu::pure]] char square(char x);
 
int f(int a) { return rand() % a; }
 
int main(int argc, char* argv[])
{
  char x = square(argv[0][0]);
  argc = static_cast<unsigned char>(argv[0][0]);
  char y = square(argv[0][0]);
  return x + y + argc;
}
cpp/attribute/pure5.cpp.asm
0000000000000000 <f(int)>:
   0:	endbr64 
   4:	push   rbx
   5:	mov    ebx,edi
   7:	call   c <f(int)+0xc>
			8: R_X86_64_PLT32	rand-0x4
   c:	cdq    
   d:	idiv   ebx
   f:	pop    rbx
  10:	mov    eax,edx
  12:	ret    
 
Disassembly of section .text.startup:
 
0000000000000000 <main>:
   0:	endbr64 
   4:	mov    rax,QWORD PTR [rsi]
   7:	push   rbx
   8:	movsx  edi,BYTE PTR [rax]
   b:	mov    ebx,edi
   d:	call   12 <main+0x12>
			e: R_X86_64_PLT32	square(char)-0x4
  12:	movzx  ebx,bl
  15:	movsx  eax,al
  18:	lea    eax,[rbx+rax*2]
  1b:	pop    rbx
  1c:	ret    
Utilisation en écriture au milieu : pas d'optimisation.

cpp/attribute/pure6.cpp
#include <cstdlib>
 
[[gnu::pure]] char square(char x);
 
int f(int a) { return rand() % a; }
 
int main(int argc, char* argv[])
{
  char x = square(argv[0][0]);
  argv[0][0] = x;
  char y = square(argv[0][0]);
  return x + y + argc;
}
cpp/attribute/pure6.cpp.asm
0000000000000000 <f(int)>:
   0:	endbr64 
   4:	push   rbx
   5:	mov    ebx,edi
   7:	call   c <f(int)+0xc>
			8: R_X86_64_PLT32	rand-0x4
   c:	cdq    
   d:	idiv   ebx
   f:	pop    rbx
  10:	mov    eax,edx
  12:	ret    
 
Disassembly of section .text.startup:
 
0000000000000000 <main>:
   0:	endbr64 
   4:	push   r13
   6:	push   r12
   8:	mov    r12d,edi
   b:	push   rbp
   c:	mov    rbp,rsi
   f:	push   rbx
  10:	sub    rsp,0x8
  14:	mov    r13,QWORD PTR [rsi]
  17:	movsx  edi,BYTE PTR [r13+0x0]
  1c:	call   21 <main+0x21>
			1d: R_X86_64_PLT32	square(char)-0x4
  21:	movsx  ebx,al
  24:	mov    BYTE PTR [r13+0x0],bl
  28:	mov    rax,QWORD PTR [rbp+0x0]
  2c:	movsx  edi,BYTE PTR [rax]
  2f:	call   34 <main+0x34>
			30: R_X86_64_PLT32	square(char)-0x4
  34:	add    rsp,0x8
  38:	movsx  eax,al
  3b:	add    ebx,eax
  3d:	lea    eax,[rbx+r12*1]
  41:	pop    rbx
  42:	pop    rbp
  43:	pop    r12
  45:	pop    r13
  47:	ret    

__attribute__((weak)) et extern

  • Explication

Cette information va être utile au lieur.

Chaque symbole (variable globale ou fonction) peut être “strong” ou weak. Si un symbole “strong” existe, les symboles weak seront ignorés. Si deux symboles weak existent sans symbole “strong”, le lieur prendra aléatoirement l'un des deux (voir lieur). Si deux symboles “strong” existent, le lieur va générer une erreur duplicate symbol.

Il est aussi possible de définir un prototype ou la déclaration d'une variable globale extern en weak. Dans ce cas, si le symbole n'est pas défini, le lieur ne posera pas de problème (pas de undefined reference to) et considérera que le symbole est à l'adresse null (ne pas lire ces symboles).

  • Exemple
cpp/attribute/weak1.cpp
#include <iostream>
 
// Défini dans le header
extern int __attribute__((weak)) variable;
extern unsigned char __attribute__((weak)) variableNull;
 
// Défini dans le code source.
int __attribute__((weak)) variable = 1;
 
int main()
{
  // variable a bien un pointeur non nullptr.
  std::cout << "variable : nullptr ? " << std::boolalpha
            << (&variable == nullptr) << " et vaut " << variable << ".\n";
  // variableNull est bien une variable avec un pointeur nullptr.
  std::cout << "variableNull : nullptr ? " << std::boolalpha
            << (&variableNull == nullptr) << " et ne peut être lu.\n";
}

Symboles :

cpp/attribute/weak1.cpp.nm
0000000000000000 V variable
                 w variableNull

Résultat dans la sortie standard:

cpp/attribute/weak1.out
variable : nullptr ? false et vaut 1.
variableNull : nullptr ? true et ne peut être lu.

restrict

Mot clé pour indiquer qu'une zone mémoire n'est accédée que par un seul pointeur. Cette restriction n'est évidemment pas thread-safe.

Voir l'exemple de Wikipédia sur restrict Archive du 16/04/2020 le 04/01/2021.

SourceCode généré
cpp/attribute/restrict1.c
void updatePtrs(int* ptrA, int* ptrB, const int* val)
{
  *ptrA += *val;
  *ptrB += *val;
}
cpp/attribute/restrict1.c.asm
0000000000000000 <updatePtrs>:
   0:	endbr64 
   4:	mov    eax,DWORD PTR [rdx]
   6:	add    DWORD PTR [rdi],eax
   8:	mov    eax,DWORD PTR [rdx]
   a:	add    DWORD PTR [rsi],eax
   c:	ret    
cpp/attribute/restrict2.c
void updatePtrs(int* restrict ptrA, int* restrict ptrB, const int* restrict val)
{
  *ptrA += *val;
  *ptrB += *val;
}
cpp/attribute/restrict2.c.asm
0000000000000000 <updatePtrs>:
   0:	endbr64 
   4:	mov    eax,DWORD PTR [rdx]
   6:	add    DWORD PTR [rdi],eax
   8:	add    DWORD PTR [rsi],eax
   a:	ret    

Avec restrict, il n'y a pas besoin de relire le contenu de *val entre les deux instructions.

lang/c/attribut.txt · Dernière modification : 2021/01/05 21:50 de root