Nome mutilado - Name mangling

Na construção do compilador , a manipulação de nomes (também chamada de decoração de nomes ) é uma técnica usada para resolver vários problemas causados ​​pela necessidade de resolver nomes exclusivos para entidades de programação em muitas linguagens de programação modernas .

Ele fornece uma maneira de codificar informações adicionais no nome de uma função , estrutura , classe ou outro tipo de dados para passar mais informações semânticas do compilador para o vinculador .

A necessidade de mutilar o nome surge quando a linguagem permite que diferentes entidades sejam nomeadas com o mesmo identificador , desde que ocupem um namespace diferente (normalmente definido por um módulo, classe ou diretiva de namespace explícita ) ou tenham assinaturas diferentes (como em função sobrecarregando ). É necessário nesses casos de uso porque cada assinatura pode exigir uma convenção de chamada especializada diferente no código de máquina.

Qualquer código-objeto produzido por compiladores é geralmente vinculado a outras partes do código-objeto (produzido pelo mesmo ou por outro compilador) por um tipo de programa chamado linker . O vinculador precisa de muitas informações sobre cada entidade do programa. Por exemplo, para vincular corretamente uma função, ela precisa de seu nome, o número de argumentos e seus tipos, e assim por diante.

As linguagens de programação simples dos anos 1970, como C , apenas distinguiam as partes do código por seus nomes, ignorando quaisquer outras informações como tipos de parâmetros ou tipos de retorno. Linguagens de programação posteriores, como C ++ , definiram requisitos mais rígidos para partes do código a serem consideradas "iguais", como os tipos de parâmetro, tipo de retorno e convenção de chamada de uma função. Esses requisitos permitem o uso de sobrecarga de função, bem como a detecção de vários bugs (como o uso de diferentes definições de uma função para compilar diferentes arquivos de origem). Esses requisitos mais rígidos precisavam funcionar com as ferramentas e convenções existentes, portanto, requisitos adicionais eram codificados no nome do símbolo, uma vez que essa era a única informação que o vinculador tradicional tinha sobre um símbolo.

Outro uso de mutilação de nome é detectar alterações adicionais não relacionadas à assinatura, como pureza da função ou se ela pode potencialmente lançar uma exceção ou acionar a coleta de lixo. Um exemplo de uma linguagem de fazer isso é D . Trata-se de uma verificação de erros mais simplificada. Por exemplo, funções int f();e int g(int) pure;podem ser compiladas em um arquivo de objeto, mas então suas assinaturas mudadas para float f(); int g(int);e usadas para compilar outra fonte que o chama. No momento do link, o vinculador detectará que não há função f(int)e retornará um erro. Da mesma forma, o vinculador não será capaz de detectar se o tipo de retorno de fé diferente e retornar um erro. Caso contrário, convenções de chamada incompatíveis seriam usadas e, provavelmente, produziriam o resultado errado ou travariam o programa. A mutilação geralmente não captura todos os detalhes do processo de chamada. Por exemplo, ele não evita totalmente erros como alterações de membros de dados de uma estrutura ou classe. Por exemplo, struct S {}; void f(S) {}poderia ser compilado em um arquivo de objeto e, em seguida, a definição de Salterado para ser struct S { int x; };e usado na compilação de uma chamada para f(S()). Nesses casos, o compilador normalmente usará uma convenção de chamada diferente, mas em ambos os casos firá mangle para o mesmo nome, então o vinculador não detectará este problema, e o resultado geralmente será um travamento ou corrupção de dados ou memória em tempo de execução .

Exemplos

C

Embora a mutilação de nomes geralmente não seja exigida ou usada por linguagens que não suportam sobrecarga de função , como C e Pascal clássico , elas a usam em alguns casos para fornecer informações adicionais sobre uma função. Por exemplo, os compiladores direcionados às plataformas Microsoft Windows oferecem suporte a uma variedade de convenções de chamada , que determinam a maneira como os parâmetros são enviados para as sub-rotinas e os resultados são retornados. Como as diferentes convenções de chamada são incompatíveis umas com as outras, os compiladores misturam símbolos com códigos detalhando qual convenção deve ser usada para chamar a rotina específica.

O esquema de mutilação foi estabelecido pela Microsoft e foi informalmente seguido por outros compiladores, incluindo Digital Mars, Borland e GNU GCC, ao compilar o código para as plataformas Windows. O esquema se aplica até mesmo a outras linguagens, como Pascal , D , Delphi , Fortran e C # . Isso permite que sub-rotinas escritas nessas linguagens chamem, ou sejam chamadas por, bibliotecas Windows existentes usando uma convenção de chamada diferente de seu padrão.

Ao compilar os seguintes exemplos C:

int _cdecl    f (int x) { return 0; }
int _stdcall  g (int y) { return 0; }
int _fastcall h (int z) { return 0; }

Compiladores de 32 bits emitem, respectivamente:

_f
_g@4
@h@4

Nos esquemas stdcalle fastcallmangling, a função é codificada como e respectivamente, onde X é o número de bytes, em decimal, do (s) argumento (s) na lista de parâmetros (incluindo aqueles passados ​​em registradores, para fastcall). No caso de , o nome da função é apenas prefixado por um sublinhado. _name@X@name@Xcdecl

A convenção de 64 bits no Windows (Microsoft C) não tem sublinhado à esquerda. Essa diferença pode, em alguns casos raros, levar a itens externos não resolvidos ao portar esse código para 64 bits. Por exemplo, o código Fortran pode usar 'alias' para vincular a um método C por nome da seguinte maneira:

SUBROUTINE f()
!DEC$ ATTRIBUTES C, ALIAS:'_f' :: f
END SUBROUTINE

Isso irá compilar e vincular bem em 32 bits, mas gerará um externo não resolvido _fem 64 bits. Uma solução alternativa para isso é não usar 'alias' (em que os nomes dos métodos normalmente precisam ser capitalizados em C e Fortran). Outra é usar a opção BIND:

SUBROUTINE f() BIND(C,NAME="f")
END SUBROUTINE

Em C, a maioria dos compiladores também destrói funções e variáveis ​​estáticas (e em C ++ funções e variáveis ​​declaradas estáticas ou colocadas no namespace anônimo) em unidades de tradução usando as mesmas regras de mutilação usadas em suas versões não estáticas. Se funções com o mesmo nome (e parâmetros para C ++) também forem definidas e usadas em diferentes unidades de tradução, também serão destruídas com o mesmo nome, potencialmente levando a um conflito. No entanto, eles não serão equivalentes se forem chamados em suas respectivas unidades de tradução. Normalmente, os compiladores são livres para emitir mutações arbitrárias para essas funções, porque é ilegal acessá-las diretamente de outras unidades de tradução, portanto, eles nunca precisarão ser vinculados a códigos de objetos diferentes (nunca é necessário vinculá-los). Para evitar conflitos de ligação, os compiladores usarão a manipulação padrão, mas usarão os chamados símbolos 'locais'. Ao vincular muitas dessas unidades de tradução, pode haver várias definições de uma função com o mesmo nome, mas o código resultante só chamará uma ou outra, dependendo de qual unidade de tradução veio. Isso geralmente é feito usando o mecanismo de realocação .

C ++

Os compiladores C ++ são os usuários mais comuns de manipulação de nomes. Os primeiros compiladores C ++ foram implementados como tradutores para o código-fonte C , que seria então compilado por um compilador C para o código-objeto; por causa disso, os nomes dos símbolos tinham que estar em conformidade com as regras do identificador C. Mesmo mais tarde, com o surgimento de compiladores que produziam código de máquina ou assembly diretamente, o vinculador do sistema geralmente não suportava símbolos C ++ e a mutilação ainda era necessária.

A linguagem C ++ não define um esquema de decoração padrão, portanto, cada compilador usa o seu próprio. C ++ também tem recursos de linguagem complexos, como classes , modelos , namespaces e sobrecarga de operador , que alteram o significado de símbolos específicos com base no contexto ou uso. Os metadados sobre essas características podem ser eliminados mutilando (decorando) o nome de um símbolo . Como os sistemas de mutilação de nomes para tais recursos não são padronizados entre os compiladores, poucos vinculadores podem vincular o código-objeto que foi produzido por diferentes compiladores.

Exemplo simples

Uma única unidade de tradução C ++ pode definir duas funções chamadas f():

int  f () { return 1; }
int  f (int)  { return 0; }
void g () { int i = f(), j = f(0); }

São funções distintas, sem relação entre si, exceto pelo nome. O compilador C ++ irá, portanto, codificar as informações de tipo no nome do símbolo, o resultado sendo algo semelhante a:

int  __f_v () { return 1; }
int  __f_i (int)  { return 0; } 
void __g_v () { int i = __f_v(), j = __f_i(0); }

Mesmo que seu nome seja único, g()ainda é mutilado: a mutação de nome se aplica a todos os símbolos C ++ (aqueles que não estão em um bloco). extern "C"{}

Exemplo complexo

Os símbolos mutilados neste exemplo, nos comentários abaixo do respectivo nome do identificador, são aqueles produzidos pelos compiladores GNU GCC 3.x, de acordo com a ABI IA-64 (Itanium):

namespace wikipedia 
{
   class article 
   {
   public:
      std::string format ();  // = _ZN9wikipedia7article6formatEv

      bool print_to (std::ostream&);  // = _ZN9wikipedia7article8print_toERSo

      class wikilink 
      {
      public:
         wikilink (std::string const& name);  // = _ZN9wikipedia7article8wikilinkC1ERKSs
      };
   };
}

Todos os símbolos mutilados começam com _Z(observe que um identificador que começa com um sublinhado seguido por uma letra maiúscula é um identificador reservado em C, portanto, o conflito com os identificadores do usuário é evitado); para nomes aninhados (incluindo namespaces e classes), isso é seguido por Numa série de pares <length, id> (sendo o comprimento o comprimento do próximo identificador) e, finalmente E. Por exemplo, wikipedia::article::formattorna-se:

_ZN9wikipedia7article6formatE

Para funções, isso é seguido pelas informações de tipo; como format()é uma voidfunção, isso é simplesmente v; portanto:

_ZN9wikipedia7article6formatEv

Para print_to, o tipo padrão std::ostream(que é um typedef para std::basic_ostream<char, std::char_traits<char> >) é usado, que tem o alias especial So; uma referência a este tipo é RSo, portanto , com o nome completo da função sendo:

_ZN9wikipedia7article8print_toERSo

Como diferentes compiladores destroem as mesmas funções

Não há um esquema padronizado pelo qual até mesmo identificadores C ++ triviais são mutilados e, consequentemente, compiladores diferentes (ou até mesmo versões diferentes do mesmo compilador, ou o mesmo compilador em plataformas diferentes) mutilam símbolos públicos radicalmente diferentes (e, portanto, totalmente incompatíveis) maneiras. Considere como diferentes compiladores C ++ modificam as mesmas funções:

Compilador void h(int) void h(int, char) void h(void)
Intel C ++ 8.0 para Linux _Z1hi _Z1hic _Z1hv
HP aC ++ A.05.55 IA-64
IAR EWARM C ++ 5.4 ARM
GCC 3. x e superior
Clang 1. x e superior
IAR EWARM C ++ 7.4 ARM _Z<number>hi _Z<number>hic _Z<number>hv
GCC 2.9. x h__Fi h__Fic h__Fv
HP aC ++ A.03.45 PA-RISC
Microsoft Visual C ++ v6-v10 ( detalhes de mutilação ) ?h@@YAXH@Z ?h@@YAXHD@Z ?h@@YAXXZ
Digital Mars C ++
Borland C ++ v3.1 @h$qi @h$qizc @h$qv
OpenVMS C ++ v6.5 (modo ARM) H__XI H__XIC H__XV
OpenVMS C ++ v6.5 (modo ANSI) CXX$__7H__FIC26CDH77 CXX$__7H__FV2CB06E8
OpenVMS C ++ X7.1 IA-64 CXX$_Z1HI2DSQ26A CXX$_Z1HIC2NP3LI4 CXX$_Z1HV0BCA19V
SunPro CC __1cBh6Fi_v_ __1cBh6Fic_v_ __1cBh6F_v_
Tru64 C ++ v6.5 (modo ARM) h__Xi h__Xic h__Xv
Tru64 C ++ v6.5 (modo ANSI) __7h__Fi __7h__Fic __7h__Fv
Watcom C ++ 10.6 W?h$n(i)v W?h$n(ia)v W?h$n()v

Notas:

  • O compilador Compaq C ++ no OpenVMS VAX e Alpha (mas não IA-64) e Tru64 tem dois esquemas de mutilação de nomes. O esquema original pré-padrão é conhecido como modelo ARM e é baseado no nome mutilado descrito no Manual de Referência Anotado C ++ (ARM). Com o advento de novos recursos no C ++ padrão, particularmente os modelos , o esquema ARM se tornou cada vez mais inadequado - ele não conseguia codificar certos tipos de função ou produzia nomes mutilados de forma idêntica para funções diferentes. Portanto, ele foi substituído pelo modelo "ANSI" mais recente, que suportava todos os recursos do modelo ANSI, mas não era compatível com versões anteriores.
  • No IA-64, existe uma Interface Binária de Aplicativo (ABI) padrão (consulte os links externos ), que define (entre outras coisas) um esquema de mutilação de nome padrão e que é usado por todos os compiladores IA-64. GNU GCC 3. x , além disso, adotou o esquema de mutilação de nomes definido neste padrão para uso em outras plataformas não Intel.
  • O Visual Studio e o Windows SDK incluem o programa undnameque imprime o protótipo de função estilo C para um determinado nome mutilado.
  • No Microsoft Windows, o compilador Intel e o Clang usam o nome Visual C ++ mutilado para compatibilidade.
  • Para o compilador IAR EWARM C ++ 7.4 ARM, a melhor maneira de determinar o nome de uma função é compilar com a saída do montador ativada e examinar a saída no arquivo ".s" gerado dessa forma.

Tratamento de símbolos C ao vincular de C ++

O trabalho do idioma C ++ comum:

#ifdef __cplusplus 
extern "C" {
#endif
    /* ... */
#ifdef __cplusplus
}
#endif

é para garantir que os símbolos dentro sejam "não mutilados" - que o compilador emita um arquivo binário com seus nomes não decorados, como um compilador C faria. Como as definições da linguagem C não são mutiladas, o compilador C ++ precisa evitar referências mutiladas a esses identificadores.

Por exemplo, a biblioteca de strings padrão <string.h>, geralmente contém algo semelhante:

#ifdef __cplusplus
extern "C" {
#endif

void *memset (void *, int, size_t);
char *strcat (char *, const char *);
int   strcmp (const char *, const char *);
char *strcpy (char *, const char *);

#ifdef __cplusplus
}
#endif

Assim, códigos como:

if (strcmp(argv[1], "-x") == 0) 
    strcpy(a, argv[2]);
else 
    memset (a, 0, sizeof(a));

usa o correto, não mutilado strcmpe memset. Se o extern "C"não tivesse sido usado, o compilador (SunPro) C ++ produziria código equivalente a:

if (__1cGstrcmp6Fpkc1_i_(argv[1], "-x") == 0) 
    __1cGstrcpy6Fpcpkc_0_(a, argv[2]);
else 
    __1cGmemset6FpviI_0_ (a, 0, sizeof(a));

Como esses símbolos não existem na biblioteca de tempo de execução C ( por exemplo, libc), podem ocorrer erros de link.


Nome padronizado mutilado em C ++

Parece que a mutilação de nomes padronizados na linguagem C ++ levaria a uma maior interoperabilidade entre as implementações do compilador. No entanto, tal padronização por si só não seria suficiente para garantir a interoperabilidade do compilador C ++ e pode até criar uma falsa impressão de que a interoperabilidade é possível e segura quando não é. A mutilação de nomes é apenas um dos vários detalhes da interface binária do aplicativo (ABI) que precisam ser decididos e observados por uma implementação C ++. Outros aspectos de ABI como tratamento de exceções , layout de tabela virtual , estrutura e preenchimento de quadro de pilha , etc. também fazem com que implementações diferentes de C ++ sejam incompatíveis. Além disso, exigir uma forma particular de mutilação causaria problemas para sistemas onde os limites de implementação (por exemplo, comprimento dos símbolos) ditam um esquema de mutilação particular. Um requisito padronizado para a mutilação de nomes também impediria uma implementação em que a mutilação não fosse necessária - por exemplo, um vinculador que entendesse a linguagem C ++.

O padrão C ++, portanto, não tenta padronizar a mutilação de nomes. Ao contrário, o Manual de Referência C ++ Anotado (também conhecido como ARM , ISBN  0-201-51459-1 , seção 7.2.1c) incentiva ativamente o uso de diferentes esquemas de mutilação para evitar a vinculação quando outros aspectos da ABI são incompatíveis.

No entanto, conforme detalhado na seção acima, em algumas plataformas o C ++ ABI completo foi padronizado, incluindo a mutilação de nomes.

Efeitos do mundo real da mutilação de nomes C ++

Como os símbolos C ++ são rotineiramente exportados de DLL e arquivos de objetos compartilhados , o esquema de mutilação de nomes não é apenas uma questão interna do compilador. Compiladores diferentes (ou versões diferentes do mesmo compilador, em muitos casos) produzem esses binários sob diferentes esquemas de decoração de nomes, o que significa que os símbolos são freqüentemente não resolvidos se os compiladores usados ​​para criar a biblioteca e o programa que a usa empregou esquemas diferentes. Por exemplo, se um sistema com vários compiladores C ++ instalados (por exemplo, GNU GCC e o compilador do fornecedor do sistema operacional) desejasse instalar as Bibliotecas Boost C ++ , ele teria que ser compilado várias vezes (uma para o GCC e outra para o compilador do fornecedor).

É bom para fins de segurança que os compiladores que produzem códigos de objeto incompatíveis (códigos baseados em diferentes ABIs, em relação, por exemplo, a classes e exceções) usem diferentes esquemas de mutilação de nomes. Isso garante que essas incompatibilidades sejam detectadas na fase de vinculação, não durante a execução do software (o que pode levar a bugs obscuros e sérios problemas de estabilidade).

Por esse motivo, a decoração do nome é um aspecto importante de qualquer ABI relacionada ao C ++ .

Demangle via c ++ filt

$ c++filt -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_
Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const

Demangle via GCC ABI integrado

#include <stdio.h>
#include <stdlib.h>
#include <cxxabi.h>

int main() {
	const char *mangled_name = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_";
	int status = -1;
	char *demangled_name = abi::__cxa_demangle(mangled_name, NULL, NULL, &status);
	printf("Demangled: %s\n", demangled_name);
	free(demangled_name);
	return 0;
}

Saída:

Demangled: Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const

Java

Em Java, a assinatura de um método ou classe contém seu nome e os tipos de seus argumentos de método e valor de retorno, quando aplicável. O formato das assinaturas é documentado, já que a linguagem, o compilador e o formato de arquivo .class foram todos projetados juntos (e tiveram a orientação a objetos e a interoperabilidade universal em mente desde o início).

Criação de nomes exclusivos para classes internas e anônimas

O escopo das classes anônimas é confinado à sua classe pai, então o compilador deve produzir um nome público "qualificado" para a classe interna , para evitar conflito onde outras classes com o mesmo nome (interna ou não) existam no mesmo namespace. Da mesma forma, as classes anônimas devem ter nomes públicos "falsos" gerados para elas (já que o conceito de classes anônimas só existe no compilador, não no tempo de execução). Portanto, compilando o seguinte programa java

public class foo {
    class bar {
        public int x;
    }

    public void zark () {
        Object f = new Object () {
            public String toString() {
                return "hello";
            }
        };
    }
}

irá produzir três arquivos .class :

  • foo.class , contendo a classe principal (externa) foo
  • foo $ bar.class , contendo a classe interna nomeada foo.bar
  • foo $ 1.class , contendo a classe interna anônima (local para o método foo.zark )

Todos esses nomes de classe são válidos (já que $ símbolos são permitidos na especificação JVM) e esses nomes são "seguros" para serem gerados pelo compilador, pois a definição da linguagem Java recomenda não usar $ símbolos em definições normais de classe Java.

A resolução de nomes em Java é ainda mais complicada no tempo de execução, pois os nomes de classe totalmente qualificados são exclusivos apenas dentro de uma instância do carregador de classe específica . Classloaders são ordenados hierarquicamente e cada Thread na JVM tem um carregador de classe de contexto, então, nos casos em que duas instâncias diferentes do classloader contêm classes com o mesmo nome, o sistema primeiro tenta carregar a classe usando o classloader raiz (ou sistema) e então desce na hierarquia até o carregador de classes de contexto.

Interface Nativa Java

O suporte ao método nativo do Java permite que os programas da linguagem Java chamem programas escritos em outra linguagem (geralmente C ou C ++). Existem duas questões de resolução de nome aqui, nenhuma das quais é implementada de uma maneira particularmente padrão:

  • JVM para tradução de nome nativo - parece mais estável, já que a Oracle torna seu esquema público.
  • Troca de nomes C ++ normal - veja acima.

Pitão

Em Python , mangling é usado para atributos de classe que não se deseja que as subclasses usem, que são designados como tais, dando-lhes um nome com dois sublinhados iniciais e não mais do que um sublinhado final. Por exemplo, __thingserá mutilado, como vai ___thinge __thing_, mas __thing__e __thing___não vai. O tempo de execução do Python não restringe o acesso a tais atributos, a mutilação apenas evita colisões de nomes se uma classe derivada definir um atributo com o mesmo nome.

Ao encontrar atributos de nome mutilado, Python transforma esses nomes adicionando um único sublinhado e o nome da classe envolvente, por exemplo:

>>> class Test:
...     def __mangled_name(self):
...         pass
...     def normal_name(self):
...         pass
>>> t = Test()
>>> [attr for attr in dir(t) if "name" in attr]
['_Test__mangled_name', 'normal_name']

Pascal

Gama Turbo Pascal / Delphi da Borland

Para evitar a mutilação de nomes em Pascal, use:

exports
  myFunc name 'myFunc',
  myProc name 'myProc';

Pascal grátis

O Free Pascal oferece suporte à sobrecarga de funções e operadores, portanto, também usa a alteração de nomes para oferecer suporte a esses recursos. Por outro lado, Free Pascal é capaz de chamar símbolos definidos em módulos externos criados em outra linguagem e exportar seus próprios símbolos para serem chamados por outra linguagem. Para mais informações, consulte os Capítulos 6.2 e 7.1 do Free Pascal Programmer's Guide .

Fortran

A mutilação de nomes também é necessária em compiladores Fortran , originalmente porque a linguagem não faz distinção entre maiúsculas e minúsculas . Outros requisitos de mutilação foram impostos posteriormente na evolução da linguagem por causa da adição de módulos e outros recursos no padrão Fortran 90. O mangling caso, especialmente, é um problema comum que deve ser tratado com a fim de chamar bibliotecas Fortran, como LAPACK , de outras linguagens, como C .

Por causa da insensibilidade a maiúsculas e minúsculas, o nome de uma sub-rotina ou função FOOdeve ser convertido para um caso e formato padronizados pelo compilador para que seja vinculado da mesma maneira, independentemente do caso. Compiladores diferentes implementaram isso de várias maneiras e nenhuma padronização ocorreu. Os compiladores AIX e HP-UX Fortran convertem todos os identificadores em minúsculas foo, enquanto os compiladores Cray e Unicos Fortran convertem os identificadores em maiúsculas FOO. O compilador GNU g77 converte identificadores em minúsculas mais um sublinhado foo_, exceto que os identificadores que já contêm um sublinhado FOO_BARtêm dois sublinhados anexados foo_bar__, seguindo uma convenção estabelecida por f2c . Muitos outros compiladores, incluindo SGI 's IRIX compiladores, gfortran e Intel ' compilador Fortran s (exceto no Microsoft Windows), converter todos os identificadores para minúsculas além de um sublinhado ( foo_e foo_bar_, respectivamente). No Microsoft Windows, o compilador Intel Fortran é padronizado para maiúsculas sem sublinhado.

Os identificadores em módulos Fortran 90 devem ser ainda mais mutilados, porque o mesmo nome de procedimento pode ocorrer em módulos diferentes. Como o Fortran 2003 Standard exige que os nomes dos procedimentos do módulo não entrem em conflito com outros símbolos externos, os compiladores tendem a usar o nome do módulo e o nome do procedimento, com um marcador distinto entre eles. Por exemplo:

module m 
contains
   integer function five()
      five = 5
   end function five
end module m

Neste módulo, o nome da função será mutado como __m_MOD_five(por exemplo, GNU Fortran), m_MP_five_(por exemplo, ifort da Intel), m.five_(por exemplo, sun95 da Oracle), etc. Como o Fortran não permite sobrecarregar o nome de um procedimento, mas usa Em vez disso, blocos de interface genéricos e procedimentos vinculados a tipo genérico, os nomes mutilados não precisam incorporar pistas sobre os argumentos.

A opção Fortran 2003 BIND substitui qualquer alteração de nome feita pelo compilador, conforme mostrado acima .

Ferrugem

Os nomes das funções são mutilados por padrão no Rust . No entanto, isso pode ser desabilitado pelo #[no_mangle]atributo de função. Este atributo pode ser usado para exportar funções para C, C ++ ou Objective-C. Além disso, junto com o #[start]atributo function ou #[no_main]crate, permite ao usuário definir um ponto de entrada no estilo C para o programa.

Rust usou muitas versões de esquemas de mutilação de símbolos que podem ser selecionados em tempo de compilação com uma -Z symbol-mangling-versionopção. Os seguintes manglers são definidos:

  • legacyUm mangling de estilo C ++ baseado no Itanium IA-64 C ++ ABI. Os símbolos começam com _ZN, e hashes de nome de arquivo são usados ​​para desambiguação. Usado desde Rust 1.9.
  • v0Uma versão aprimorada do esquema legado, com alterações para Rust. Os símbolos começam com _R. O polimorfismo pode ser codificado. As funções não têm tipos de retorno codificados (Rust não tem sobrecarga). Os nomes Unicode usam punycode modificado . A compactação (referência anterior) usa endereçamento baseado em bytes. Usado desde Rust 1.37.

Exemplos são fornecidos nos symbol-namestestes de ferrugem .

Objective-C

Essencialmente, existem duas formas de método em Objective-C , o método de classe ("estático") e o método de instância . Uma declaração de método em Objective-C tem o seguinte formato:

+ (return-type) name0:parameter0 name1:parameter1 ...
– (return-type) name0:parameter0 name1:parameter1 ...

Os métodos de classe são identificados por +, os métodos de instância usam -. Uma declaração típica de método de classe pode ser assim:

+ (id) initWithX: (int) number andY: (int) number;
+ (id) new;

Com métodos de instância parecidos com este:

- (id) value;
- (id) setValue: (id) new_value;

Cada uma dessas declarações de método tem uma representação interna específica. Quando compilado, cada método é nomeado de acordo com o seguinte esquema para métodos de classe:

_c_Class_name0_name1_ ...

e este, por exemplo, métodos:

_i_Class_name0_name1_ ...

Os dois pontos na sintaxe Objective-C são convertidos em sublinhados. Portanto, o método da classe Objective-C , se pertencer à classe, seria traduzido como , e o método da instância (pertencente à mesma classe) seria traduzido para . + (id) initWithX: (int) number andY: (int) number;Point_c_Point_initWithX_andY_- (id) value;_i_Point_value

Cada um dos métodos de uma classe é rotulado dessa forma. No entanto, para pesquisar um método ao qual uma classe possa responder, seria tedioso se todos os métodos fossem representados dessa maneira. Cada um dos métodos é atribuído a um símbolo exclusivo (como um inteiro). Esse símbolo é conhecido como seletor . Em Objective-C, pode-se gerenciar os seletores diretamente - eles têm um tipo específico em Objective-C - SEL.

Durante a compilação, é construída uma tabela que mapeia a representação textual, como _i_Point_value, para seletores (que recebem um tipo SEL). Gerenciar seletores é mais eficiente do que manipular a representação textual de um método. Observe que um seletor corresponde apenas ao nome de um método, não à classe a que pertence - diferentes classes podem ter diferentes implementações de um método com o mesmo nome. Por causa disso, as implementações de um método também recebem um identificador específico, conhecido como ponteiros de implementação, e também recebem um tipo IMP,.

Os envios de mensagens são codificados pelo compilador como chamadas para a função, ou um de seus primos, onde é o receptor da mensagem, e determina o método a ser chamado. Cada classe tem sua própria tabela que mapeia os seletores para suas implementações - o ponteiro de implementação especifica onde a implementação real do método reside na memória. Existem tabelas separadas para métodos de classe e instância. Além de ser armazenado nos para tabelas de pesquisa, as funções são essencialmente anônimas. id objc_msgSend (id receiver, SEL selector, ...)receiverSELSELIMP

O SELvalor de um seletor não varia entre as classes. Isso permite o polimorfismo .

O tempo de execução Objective-C mantém informações sobre o argumento e os tipos de retorno dos métodos. No entanto, essas informações não fazem parte do nome do método e podem variar de classe para classe.

Como Objective-C não oferece suporte a namespaces , não há necessidade de alterar os nomes das classes (que aparecem como símbolos nos binários gerados).

Rápido

O Swift mantém metadados sobre funções (e mais) nos símbolos mutilados que se referem a eles. Esses metadados incluem o nome da função, atributos, nome do módulo, tipos de parâmetro, tipo de retorno e muito mais. Por exemplo:

O nome mutilado para um método func calculate(x: int) -> intde uma MyClassclasse no módulo testé _TFC4test7MyClass9calculatefS0_FT1xSi_Si, para 2014 Swift. Os componentes e seus significados são os seguintes:

  • _T: O prefixo para todos os símbolos Swift. Tudo vai começar com isso.
  • F: Função não curried.
  • C: Função de uma classe, ou seja, um método
  • 4test: Nome do módulo, prefixado com seu comprimento.
  • 7MyClass: Nome da classe à qual a função pertence, prefixado com seu comprimento.
  • 9calculate: Nome da função, prefixado com seu comprimento.
  • f: O atributo da função. Neste caso, 'f', que significa uma função normal.
  • S0: Designa o tipo do primeiro parâmetro (ou seja, a instância da classe) como o primeiro na pilha de tipos (aqui MyClassnão está aninhado e, portanto, tem índice 0).
  • _FT: Isso inicia a lista de tipos para a tupla de parâmetro da função.
  • 1x: Nome externo do primeiro parâmetro da função.
  • Si: Indica o tipo Swift embutido Swift.Int para o primeiro parâmetro.
  • _Si: O tipo de retorno: novamente Swift.Int.

A alteração de versões desde o Swift 4.0 está documentada oficialmente. Ele mantém alguma semelhança com o Itanium.

Veja também

Referências

links externos