En la construcción de compiladores , la alteración de nombres (también llamada decoración de nombres ) es una técnica utilizada para resolver diversos problemas causados por la necesidad de resolver nombres únicos para entidades de programación en muchos lenguajes de programación modernos .
Proporciona medios para codificar información agregada en el nombre de una función , estructura , clase u otro tipo de datos , para pasar más información semántica del compilador al vinculador .
La necesidad de modificar nombres surge cuando un lenguaje permite que diferentes entidades sean nombradas con el mismo identificador siempre que ocupen un espacio de nombres diferente (generalmente definido por un módulo, clase o directiva explícita de espacio de nombres ) o tengan firmas de tipo diferentes (como en sobrecarga de funciones ). Es necesario en estos usos porque cada firma puede requerir una convención de llamada especializada diferente en el código de máquina .
Cualquier código objeto producido por compiladores generalmente se vincula con otras piezas de código objeto (producidas por el mismo compilador u otro) mediante un tipo de programa llamado vinculador . El vinculador necesita una gran cantidad de información sobre cada entidad del programa. Por ejemplo, para vincular correctamente una función necesita su nombre, la cantidad de argumentos y sus tipos, etc.
Los lenguajes de programación simples de la década de 1970, como C , solo distinguían las subrutinas por su nombre, ignorando otra información, incluidos parámetros y tipos de retorno. Los lenguajes posteriores, como C++ , definieron requisitos más estrictos para que las rutinas se consideren "iguales", como los tipos de parámetros, el tipo de retorno y la convención de llamada de una función. Estos requisitos permiten la sobrecarga de métodos y la detección de algunos errores (como el uso de diferentes definiciones de una función al compilar diferentes archivos de código fuente ). Estos requisitos más estrictos debían funcionar con las herramientas y convenciones de programación existentes. Por lo tanto, se codificaron requisitos adicionales en el nombre del símbolo, ya que esa era la única información que tenía un enlazador tradicional sobre un símbolo.
Otro uso de la manipulación de nombres es para detectar cambios agregados no relacionados con la firma, como la pureza de la función, o si potencialmente puede generar una excepción o desencadenar la recolección de basura . Un ejemplo de un lenguaje que hace esto es D. [1] [2] Se trata más bien de una comprobación de errores simplificada. Por ejemplo, las funciones int f();
y int g(int) pure;
podrían compilarse en un archivo objeto, pero luego sus firmas cambiaron float f(); int g(int);
y se usaron para compilar otra fuente que lo llame. En el momento del enlace, el enlazador detectará que no hay ninguna función f(int)
y devolverá un error. De manera similar, el vinculador no podrá detectar que el tipo de retorno f
es diferente y devolverá un error. De lo contrario, se utilizarían convenciones de llamada incompatibles y muy probablemente producirían un resultado incorrecto o bloquearían el programa. Mangling no suele capturar todos los detalles del proceso de llamada. Por ejemplo, no previene por completo errores como cambios de miembros de datos de una estructura o clase. Por ejemplo, struct S {}; void f(S) {}
podría compilarse en un archivo objeto, luego cambiarse la definición S
y struct S { int x; };
usarse en la compilación de una llamada a f(S())
. En tales casos, el compilador generalmente usará una convención de llamada diferente, pero en ambos casos f
cambiará al mismo nombre, por lo que el vinculador no detectará este problema y el resultado generalmente será una falla o corrupción de datos o memoria en tiempo de ejecución. .
Aunque la manipulación de nombres generalmente no es requerida ni utilizada por lenguajes que no soportan la sobrecarga de funciones , como C y Pascal clásico , la usan en algunos casos para proporcionar información adicional sobre una función. Por ejemplo, los compiladores destinados a plataformas Microsoft Windows admiten una variedad de convenciones de llamada , que determinan la manera en que se envían los parámetros a las subrutinas y se devuelven los resultados. Debido a que las diferentes convenciones de llamada son incompatibles entre sí, los compiladores alteran los símbolos con códigos que detallan qué convención debe usarse para llamar a la rutina específica.
El esquema de manipulación para Windows fue establecido por Microsoft y ha sido seguido informalmente por otros compiladores, incluidos Digital Mars , Borland y GNU Compiler Collection (GCC), al compilar código para las plataformas Windows. El esquema se aplica incluso a otros lenguajes, como Pascal , D , Delphi , Fortran y C# . Esto permite que las subrutinas escritas en esos idiomas llamen o sean llamadas por bibliotecas existentes de Windows utilizando una convención de llamada diferente a la predeterminada.
Al compilar los siguientes ejemplos de C:
int _cdecl f ( int x ) { retorno 0 ; } int _stdcall g ( int y ) { return 0 ; } int _fastcall h ( int z ) { retorno 0 ; }
Los compiladores de 32 bits emiten, respectivamente:
_F_g@4@h@4
En los esquemas de manipulación stdcall
y fastcall
, la función se codifica como y respectivamente, donde X es el número de bytes, en decimal, de los argumentos en la lista de parámetros (incluidos los pasados en registros, para llamada rápida). En el caso de , el nombre de la función simplemente tiene como prefijo un guión bajo._name@X
@name@X
cdecl
La convención de 64 bits en Windows (Microsoft C) no tiene guión bajo inicial. En algunos casos excepcionales, esta diferencia puede provocar problemas externos no resueltos al migrar dicho código a 64 bits. Por ejemplo, el código Fortran puede usar 'alias' para vincular un método C por nombre de la siguiente manera:
SUBRUTINA f () !DEC$ ATRIBUTOS C, ALIAS:'_f' :: f FIN SUBRUTINA
Esto compilará y vinculará bien en 32 bits, pero generará un externo sin resolver _f
en 64 bits. Una solución para esto es no utilizar ningún 'alias' (en el que los nombres de los métodos normalmente deben estar en mayúscula en C y Fortran). Otra es utilizar la opción BIND:
SUBRUTINA f () BIND ( C , NOMBRE = "f" ) FINAL SUBRUTINA
En C, la mayoría de los compiladores también modifican funciones y variables estáticas (y en C++ funciones y variables declaradas estáticas o colocadas en el espacio de nombres anónimo) en unidades de traducción utilizando las mismas reglas de manipulación que para sus versiones no estáticas. Si funciones con el mismo nombre (y parámetros para C++) también se definen y utilizan en diferentes unidades de traducción, también cambiarán al mismo nombre, lo que podría provocar un conflicto. Sin embargo, no serán equivalentes si se denominan en sus respectivas unidades de traducción. Los compiladores generalmente son libres de emitir modificaciones arbitrarias para estas funciones, porque es ilegal acceder a ellas directamente desde otras unidades de traducción, por lo que nunca necesitarán vincular diferentes códigos objeto (nunca es necesario vincularlos). Para evitar conflictos de vinculación, los compiladores utilizarán manipulación estándar, pero utilizarán los llamados símbolos "locales". Al vincular muchas de estas unidades de traducción, puede haber múltiples definiciones de una función con el mismo nombre, pero el código resultante solo llamará a una u otra dependiendo de la unidad de traducción de donde proviene. Esto generalmente se hace mediante el mecanismo de reubicación .
Los compiladores de C++ son los usuarios más extendidos de la manipulación de nombres. Los primeros compiladores de C++ se implementaron como traductores del código fuente de C , que luego sería compilado por un compilador de C en código objeto; Debido a esto, los nombres de los símbolos tenían que ajustarse a las reglas de identificador de C. Incluso más tarde, con la aparición de compiladores que producían código de máquina o ensamblador directamente, el enlazador del sistema generalmente no soportaba símbolos de C++ y todavía era necesario modificarlo.
El lenguaje C++ no define un esquema de decoración estándar, por lo que cada compilador utiliza el suyo propio. C++ también tiene características de lenguaje complejas, como clases , plantillas , espacios de nombres y sobrecarga de operadores , que alteran el significado de símbolos específicos según el contexto o el uso. Los metadatos sobre estas características se pueden eliminar de la ambigüedad modificando (decorando) el nombre de un símbolo . Debido a que los sistemas de manipulación de nombres para tales características no están estandarizados entre los compiladores, pocos enlazadores pueden vincular código objeto producido por diferentes compiladores.
Una única unidad de traducción de C++ podría definir dos funciones denominadas f()
:
intf ( ) { retorno 1 ; } intf ( int ) { retorno 0 ; _ } vacío g () { int yo = f (), j = f ( 0 ); }
Se trata de funciones distintas, sin relación entre sí aparte del nombre. Por lo tanto, el compilador de C++ codificará la información de tipo en el nombre del símbolo, siendo el resultado algo parecido:
int __f_v () { retorno 1 ; } int __f_i ( int ) { retorno 0 ; } vacío __g_v () { int i = __f_v (), j = __f_i ( 0 ); }
Aunque su nombre es único, g()
todavía está alterado: la alteración de nombres se aplica a todos los símbolos de C++ (excepto los de un bloque).extern "C"{}
Los símbolos alterados en este ejemplo, en los comentarios debajo del nombre del identificador respectivo, son los producidos por los compiladores GNU GCC 3.x, de acuerdo con la ABI IA-64 (Itanium):
espacio de nombres wikipedia { artículo de clase { público : std :: formato de cadena (); // = _ZN9wikipedia7artículo6formatoEv bool print_to ( std :: ostream & ); // = _ZN9wikipedia7article8print_toERSo clase wikilink { público : wikilink ( std :: cadena constante y nombre ); // = _ZN9wikipedia7article8wikilinkC1ERKSs }; }; }
Todos los símbolos alterados comienzan con _Z
(tenga en cuenta que un identificador que comienza con un guión bajo seguido de una letra mayúscula es un identificador reservado en C, por lo que se evita el conflicto con los identificadores de usuario); para nombres anidados (incluidos espacios de nombres y clases), esto va seguido de N
, luego una serie de pares <longitud, id> (la longitud es la longitud del siguiente identificador) y, finalmente E
, . Por ejemplo, wikipedia::article::format
se convierte en:
_ZN9wikipedia7artículo6formatoE
Para las funciones, esto va seguido de la información de tipo; como format()
es una void
función, esto es simplemente v
; por eso:
_ZN9wikipedia7artículo6formatoEv
Para , se utiliza print_to
el tipo estándar std::ostream
(que es un typedef para ), que tiene el alias especial ; por lo tanto, una referencia a este tipo es , siendo el nombre completo de la función:std::basic_ostream<char, std::char_traits<char> >
So
RSo
_ZN9wikipedia7article8print_toERSo
No existe un esquema estandarizado mediante el cual incluso los identificadores triviales de C++ sean destrozados y, en consecuencia, diferentes compiladores (o incluso diferentes versiones del mismo compilador, o el mismo compilador en diferentes plataformas) destrocen los símbolos públicos de forma radicalmente diferente (y por lo tanto totalmente incompatible). maneras. Considere cómo diferentes compiladores de C++ manipulan las mismas funciones:
Notas:
undname
que imprime el prototipo de función de estilo C para un nombre alterado determinado.El trabajo del lenguaje común de C++:
#ifdef __cplusplus extern "C" { #endif /* ... */ #ifdef __cplusplus } #endif
es garantizar que los símbolos que contiene estén "destrozados": que el compilador emita un archivo binario con sus nombres sin decorar, como lo haría un compilador de C. Como las definiciones del lenguaje C no están alteradas, el compilador de C++ debe evitar alterar las referencias a estos identificadores.
Por ejemplo, la biblioteca de cadenas estándar <string.h>
generalmente contiene algo parecido a:
#ifdef __cplusplus externo "C" { #endif vacío * memset ( vacío * , int , tamaño_t ); char * strcat ( char * , const char * ); int strcmp ( const char * , const char * ); char * strcpy ( char * , const char * ); #ifdef __cplusplus } #endif
Así, código como:
if ( strcmp ( argv [ 1 ], "-x" ) == 0 ) strcpy ( a , argv [ 2 ]); else memset ( a , 0 , tamaño de ( a ));
utiliza el correcto, sin destrozar strcmp
y memset
. Si extern "C"
no se hubiera utilizado, el compilador (SunPro) C++ produciría un código equivalente a:
if ( __1cGstrcmp6Fpkc1_i_ ( argv [ 1 ], "-x" ) == 0 ) __1cGstrcpy6Fpcpkc_0_ ( a , argv [ 2 ]); else __1cGmemset6FpviI_0_ ( a , 0 , tamaño de ( a ));
Dado que esos símbolos no existen en la biblioteca de ejecución de C ( por ejemplo, libc), se producirían errores de enlace.
Parecería que la manipulación estandarizada de nombres en el lenguaje C++ conduciría a una mayor interoperabilidad entre las implementaciones del compilador. Sin embargo, tal estandarización por sí sola no sería suficiente para garantizar la interoperabilidad del compilador de C++ e incluso podría crear una falsa impresión de que la interoperabilidad es posible y segura cuando no lo es. La manipulación de nombres es sólo uno de los varios detalles de la interfaz binaria de la aplicación (ABI) que una implementación de C++ debe decidir y observar. Otros aspectos de ABI, como el manejo de excepciones , el diseño de la tabla virtual , la estructura y el relleno del marco de la pila , también hacen que las diferentes implementaciones de C++ sean incompatibles. Además, requerir una forma particular de manipulación causaría problemas en los sistemas donde los límites de implementación (por ejemplo, la longitud de los símbolos) dictan un esquema de manipulación particular. Un requisito estandarizado para la manipulación de nombres también evitaría una implementación en la que la manipulación no fuera necesaria en absoluto (por ejemplo, un enlazador que entendiera el lenguaje C++).
Por lo tanto, el estándar C++ no intenta estandarizar la manipulación de nombres. Por el contrario, el Manual de referencia anotado de C++ (también conocido como ARM , ISBN 0-201-51459-1 , sección 7.2.1c) fomenta activamente el uso de diferentes esquemas de manipulación para evitar la vinculación cuando otros aspectos de la ABI son incompatibles.
Sin embargo, como se detalla en la sección anterior, en algunas plataformas [6] se ha estandarizado la ABI de C++ completa, incluida la manipulación de nombres.
Debido a que los símbolos C++ se exportan rutinariamente desde DLL y archivos de objetos compartidos , el esquema de manipulación de nombres no es simplemente una cuestión interna del compilador. Diferentes compiladores (o diferentes versiones del mismo compilador, en muchos casos) producen dichos archivos binarios bajo diferentes esquemas de decoración de nombres, lo que significa que los símbolos frecuentemente no se resuelven si los compiladores utilizados para crear la biblioteca y el programa que la utiliza emplearon diferentes esquemas. Por ejemplo, si un sistema con múltiples compiladores de C++ instalados (por ejemplo, GNU GCC y el compilador del proveedor del sistema operativo) desea instalar las bibliotecas Boost C++ , tendría que compilarse varias veces (una para GCC y otra para el compilador del proveedor).
Es bueno por motivos de seguridad que los compiladores que producen códigos objeto incompatibles (códigos basados en diferentes ABI, con respecto, por ejemplo, a clases y excepciones) utilicen diferentes esquemas de manipulación de nombres. Esto garantiza que estas incompatibilidades se detecten en la fase de vinculación, no durante la ejecución del software (lo que podría provocar errores oscuros y problemas graves de estabilidad).
Por esta razón, la decoración de nombres es un aspecto importante de cualquier ABI relacionada con C++ .
Hay casos, particularmente en bases de código grandes y complejas, donde puede ser difícil o poco práctico asignar el nombre alterado emitido dentro de un mensaje de error del vinculador al token/nombre de variable correspondiente particular en la fuente. Este problema puede hacer que la identificación de los archivos fuente relevantes sea muy difícil para los ingenieros de compilación o pruebas, incluso si solo se utilizan un compilador y un vinculador. Los demanglers (incluidos aquellos dentro de los mecanismos de informe de errores del enlazador) a veces ayudan, pero el propio mecanismo de desmantelamiento puede descartar información desambiguadora crítica.
$ c++filt -n _ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_ Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const
#incluye <stdio.h> #incluye <stdlib.h> #incluye <cxxabi.h> int main () { const char * mangled_name = "_ZNK3MapI10StringName3RefI8GDScriptE10ComparatorIS0_E16DefaultAllocatorE3hasERKS0_" ; estado entero = -1 ; char * demangled_name = abi :: __cxa_demangle ( mangled_name , NULL , NULL y estado ) ; printf ( "Demandado: %s \n " , nombre_demandado ); gratis ( nombre_demandado ); devolver 0 ; }
Producción:
Demangled: Map<StringName, Ref<GDScript>, Comparator<StringName>, DefaultAllocator>::has(StringName const&) const
En Java , la firma de un método o clase contiene su nombre y los tipos de argumentos de su método y el valor de retorno, cuando corresponda. El formato de las firmas está documentado, ya que el lenguaje, el compilador y el formato del archivo .class se diseñaron juntos (y tuvieron en mente la orientación a objetos y la interoperabilidad universal desde el principio).
El alcance de las clases anónimas se limita a su clase principal, por lo que el compilador debe producir un nombre público "calificado" para la clase interna , para evitar conflictos cuando existen otras clases con el mismo nombre (interno o no) en el mismo espacio de nombres. De manera similar, las clases anónimas deben tener nombres públicos "falsos" generados para ellas (ya que el concepto de clases anónimas solo existe en el compilador, no en el tiempo de ejecución). Entonces, compilando el siguiente programa Java:
public class foo { barra de clase { public int x ; } public void zark () { Objeto f = nuevo Objeto () { public String toString () { return "hola" ; } }; } }
producirá tres archivos .class :
Todos estos nombres de clases son válidos (ya que los símbolos $ están permitidos en la especificación JVM) y estos nombres son "seguros" para que los genere el compilador, ya que la definición del lenguaje Java recomienda no utilizar símbolos $ en las definiciones de clases Java normales.
La resolución de nombres en Java es aún más complicada en tiempo de ejecución, ya que los nombres completos de las clases son únicos sólo dentro de una instancia específica del cargador de clases . Los cargadores de clases están ordenados jerárquicamente y cada subproceso en la JVM tiene un llamado cargador de clases de contexto, por lo que en los casos en que dos instancias diferentes de cargador de clases contienen clases con el mismo nombre, el sistema primero intenta cargar la clase usando el cargador de clases raíz (o sistema). y luego baja por la jerarquía hasta el cargador de clases de contexto.
Java Native Interface , el soporte de métodos nativos de Java, permite que los programas en lenguaje Java llamen a programas escritos en otro lenguaje (generalmente C o C++). Aquí existen dos problemas de resolución de nombres, ninguno de los cuales se implementa de manera estandarizada :
En Python , la manipulación se utiliza para atributos de clase que no se desea que utilicen las subclases [8] y que se designan como tales dándoles un nombre con dos o más guiones bajos iniciales y no más de un guión bajo final. Por ejemplo, __thing
será destrozado, como lo será ___thing
y __thing_
, pero __thing__
y __thing___
no lo será. El tiempo de ejecución de Python no restringe el acceso a dichos atributos; la manipulación solo evita colisiones de nombres si una clase derivada define un atributo con el mismo nombre.
Al encontrar atributos de nombres alterados, Python transforma estos nombres anteponiendo un único guión bajo y el nombre de la clase adjunta, por ejemplo:
>>> clase Prueba : ... def __mangled_name ( self ): ... pasa ... def normal_name ( self ): ... pasa >>> t = Prueba () >>> [ attr para attr en dir ( t ) si "nombre" en attr ] ['_Test__mangled_name', 'normal_name']
Para evitar la alteración de nombres en Pascal, utilice:
exporta el nombre de myFunc 'myFunc' , el nombre de myProc 'myProc' ;
Free Pascal admite la sobrecarga de funciones y operadores, por lo que también utiliza la manipulación de nombres para admitir estas funciones. Por otro lado, Free Pascal es capaz de llamar a símbolos definidos en módulos externos creados con otro idioma y exportar sus propios símbolos para ser llamados por otro idioma. Para obtener más información, consulte los Capítulos 6.2 y 7.1 de la Guía del programador de Free Pascal.
La manipulación de nombres también es necesaria en los compiladores de Fortran , originalmente porque el lenguaje no distingue entre mayúsculas y minúsculas . Más adelante en la evolución del lenguaje se impusieron más requisitos de manipulación debido a la adición de módulos y otras características en el estándar Fortran 90. La manipulación de casos, especialmente, es un problema común que debe abordarse para llamar a bibliotecas Fortran, como LAPACK , desde otros lenguajes, como C.
Debido a que no se distinguen entre mayúsculas y minúsculas, FOO
el compilador debe convertir el nombre de una subrutina o función a un formato y caso estandarizados para que se vincule de la misma manera independientemente del caso. Diferentes compiladores han implementado esto de diversas maneras y no se ha producido ninguna estandarización. Los compiladores AIX y HP-UX Fortran convierten todos los identificadores a minúsculas foo
, mientras que los compiladores Cray y Unicos Fortran convierten los identificadores a mayúsculas FOO
. El compilador GNU g77 convierte los identificadores a minúsculas más un guión bajo foo_
, excepto que los identificadores que ya contienen un guión bajo FOO_BAR
tienen dos guiones bajos añadidos foo_bar__
, siguiendo una convención establecida por f2c . Muchos otros compiladores, incluidos los compiladores IRIX de Silicon Graphics (SGI) , GNU Fortran y el compilador Fortran de Intel (excepto en Microsoft Windows), convierten todos los identificadores a minúsculas más un guión bajo ( y , respectivamente). En Microsoft Windows, el compilador Intel Fortran utiliza de forma predeterminada mayúsculas sin guión bajo. [9]foo_
foo_bar_
Los identificadores en los módulos Fortran 90 deben modificarse aún más, porque el mismo nombre de procedimiento puede aparecer en diferentes módulos. Dado que el estándar Fortran 2003 requiere que los nombres de los procedimientos del módulo no entren en conflicto con otros símbolos externos, [10] los compiladores tienden a usar el nombre del módulo y el nombre del procedimiento, con un marcador distinto en el medio. Por ejemplo:
el módulo m contiene la función entera cinco () cinco = 5 función final cinco módulo final m
En este módulo, el nombre de la función se modificará como __m_MOD_five
(p. ej., GNU Fortran), m_MP_five_
(p. ej., ifort de Intel), m.five_
(p. ej., sun95 de Oracle), etc. Dado que Fortran no permite sobrecargar el nombre de un procedimiento, sino que utiliza En cambio, bloques de interfaz genéricos y procedimientos genéricos vinculados a tipos, los nombres mutilados no necesitan incorporar pistas sobre los argumentos.
La opción BIND de Fortran 2003 anula cualquier alteración de nombres realizada por el compilador, como se muestra arriba.
Los nombres de las funciones están alterados de forma predeterminada en Rust . Sin embargo, esto puede desactivarse mediante el #[no_mangle]
atributo de función. Este atributo se puede utilizar para exportar funciones a C, C++ u Objective-C . [11] Además, junto con el #[start]
atributo de función o el #[no_main]
atributo de caja, permite al usuario definir un punto de entrada estilo C para el programa. [12]
Rust ha utilizado muchas versiones de esquemas de manipulación de símbolos que se pueden seleccionar en tiempo de compilación con una -Z symbol-mangling-version
opción. Se definen los siguientes manglers:
legacy
Una modificación de estilo C++ basada en la ABI Itanium IA-64 C++. Los símbolos comienzan con _ZN
y los hash de los nombres de archivos se utilizan para eliminar la ambiguación. Usado desde Rust 1.9. [13]v0
Una versión mejorada del esquema heredado, con cambios para Rust. Los símbolos comienzan con _R
. El polimorfismo se puede codificar. Las funciones no tienen tipos de retorno codificados (Rust no tiene sobrecarga). Los nombres Unicode utilizan punycode modificado . La compresión (referencia inversa) utiliza direccionamiento basado en bytes. Usado desde Rust 1.37. [14]Se proporcionan ejemplos en las symbol-names
pruebas de Rust. [15]
Esencialmente existen dos formas de método en Objective-C , el método de clase ("estático") y el método de instancia . Una declaración de método en Objective-C tiene la siguiente forma:
+ ( tipo de retorno ) nombre 0 : parámetro 0 nombre 1 : parámetro 1 ...– ( tipo de retorno ) nombre 0 : parámetro 0 nombre 1 : parámetro 1 ...
Los métodos de clase se indican con +, los métodos de instancia usan -. Una declaración de método de clase típica podría verse así:
+ ( id ) initWithX: ( int ) número andY: ( int ) número ; + ( identificación ) nuevo ;
Con métodos de instancia parecidos a esto:
- ( identificación ) valor ; - ( identificación ) setValue: ( identificación ) nuevo_valor ;
Cada una de estas declaraciones de métodos tiene una representación interna específica. Cuando se compila, cada método recibe un nombre de acuerdo con el siguiente esquema para métodos de clase:
_c_ Clase _ nombre 0 _ nombre 1 _ ...
y este por ejemplo métodos:
_i_ Clase _ nombre 0 _ nombre 1 _ ...
Los dos puntos en la sintaxis de Objective-C se traducen a guiones bajos. Entonces, el método de clase Objective-C , si pertenece a la clase, se traduciría como , y el método de instancia (que pertenece a la misma clase) se traduciría como .+ (id) initWithX: (int) number andY: (int) number;
Point
_c_Point_initWithX_andY_
- (id) value;
_i_Point_value
Cada uno de los métodos de una clase están etiquetados de esta forma. Sin embargo, buscar un método al que una clase pueda responder sería tedioso si todos los métodos estuvieran representados de esta manera. A cada uno de los métodos se le asigna un símbolo único (como un número entero). Este símbolo se conoce como selector . En Objective-C, se pueden gestionar selectores directamente (tienen un tipo específico en Objective-C) SEL
.
Durante la compilación, se crea una tabla que asigna la representación textual, como _i_Point_value
, a selectores (a los que se les asigna un tipo SEL
). Administrar selectores es más eficiente que manipular la representación de texto de un método. Tenga en cuenta que un selector solo coincide con el nombre de un método, no con la clase a la que pertenece: diferentes clases pueden tener diferentes implementaciones de un método con el mismo nombre. Debido a esto, las implementaciones de un método también reciben un identificador específico, estos se conocen como punteros de implementación y también reciben un tipo IMP
.
El compilador codifica los envíos de mensajes como llamadas a la función, o a una de sus primas, donde es el receptor del mensaje y determina el método a llamar. Cada clase tiene su propia tabla que asigna selectores a sus implementaciones; el puntero de implementación especifica en qué memoria reside la implementación del método. Hay tablas separadas para métodos de clase y de instancia. Además de almacenarse en las tablas de búsqueda, las funciones son esencialmente anónimas.id objc_msgSend (id receiver, SEL selector, ...)
receiver
SEL
SEL
IMP
El SEL
valor de un selector no varía entre clases. Esto permite el polimorfismo .
El tiempo de ejecución de Objective-C mantiene información sobre el argumento y los tipos de retorno de los métodos. Sin embargo, esta información no forma parte del nombre del método y puede variar de una clase a otra.
Dado que Objective-C no admite espacios de nombres , no es necesario alterar los nombres de clases (que aparecen como símbolos en los archivos binarios generados).
Swift mantiene metadatos sobre funciones (y más) en los símbolos destrozados que se refieren a ellas. Estos metadatos incluyen el nombre de la función, los atributos, el nombre del módulo, los tipos de parámetros, el tipo de retorno y más. Por ejemplo:
El nombre alterado de un método func calculate(x: int) -> int
de una MyClass
clase en el módulo test
es _TFC4test7MyClass9calculatefS0_FT1xSi_Si
, para Swift 2014. Los componentes y sus significados son los siguientes: [16]
_T
: el prefijo de todos los símbolos Swift. Todo empezará con esto.F
: Función sin curry.C
: Función de una clase, es decir, un método4test
: Nombre del módulo, con el prefijo de su longitud.7MyClass
: Nombre de la clase a la que pertenece la función, con el prefijo de su longitud.9calculate
: Nombre de la función, con el prefijo de su longitud.f
: El atributo de la función. En este caso 'f', que significa una función normal.S0
: Designa el tipo del primer parámetro (es decir, la instancia de clase) como el primero en la pila de tipos (aquí MyClass
no está anidado y por lo tanto tiene índice 0)._FT
: Esto comienza la lista de tipos para la tupla de parámetros de la función.1x
: Nombre externo del primer parámetro de la función.Si
: Indica el tipo Swift incorporado Swift.Int para el primer parámetro._Si
: El tipo de retorno: nuevamente Swift.Int.La manipulación de versiones desde Swift 4.0 está documentada oficialmente. Conserva cierta similitud con Itanium. [17]
{{cite web}}
: Mantenimiento CS1: nombres numéricos: lista de autores ( enlace )