domingo, 25 de noviembre de 2012

Tutorial de ensamblador x86. Capítulo 3. Primeras optimizaciones



Hola a todos,

Si habéis ido siguiendo el curso, y habéis mirado los ejemplos, lo más normal es que hayáis intentado programar alguna cosilla en asm. Y también lo más normal es que toda vuestra ilusión se haya ido al garete al ver que el rendimiento de vuestra aplicación más que mejorar , empeora.

Si vuestra aplicación pierde rendimiento al aplicarle ensamblador es por el sencillo motivo que el compilador sabe "compilar" un poco mejor que vosotros :D y tiene muy claras las normas de lo que se puede hacer y lo que no se puede hacer.

Os presento ahora la lista con las cosas más típicas que se pueden mejorar en vuestra aplicación y ya veréis como poco a poco el rendimiento os irá mejorando.

1. Dependencias de datos: Esta es la más típica y normalmente la que menos se puede solucionar. Cuando nosotros ejecutamos una instrucción en ensamblador el procesador realmente lo que hace es intentar adelantarse a los acontecimientos e ir ejecutando la parte que pueda de la siguiente instrucción (si queréis saber más tendréis que googlear un poco sobre segmentación de CPU y pipeline, pero no es fácil de entender de buenas a primeras). Si nosotros encadenamos dos funciones seguidas en que el resultado de la primera forma parte de los operandos de la segunda ya no podemos ir adelantando trabajo, haciendo que el rendimiento del procesador  disminuya drásticamente. La solución se basa en intercalar otras operaciones con otros operandos en medio de las zonas conflictivas a fin de que el procesador pueda ir adelantando trabajo de otras operaciones. El problema de hacer esto es que el código queda desordenado y es de difícil comprensión.

2. Saltos condicionales: Cada vez que vamos ha hacer un salto de código (jmp, je, jg , jl) el procesador intenta adivinar que será lo más probable y ya lo empieza a precalcular. Esto significa que hay un cierto número  de veces que el procesador hará trabajo por que si, ya que la comparación no será como el se esperaba. Hay casos en que el circuito que se encarga de preveer por donde irá nuestro código falla bastante y es extremadamente complejo de predecir, así que el único consejo que os puedo dar es que minimicéis el número de saltos en la medida de lo posible.

3. Acceso a memoria: Da igual que memoria tengáis o cuan cara y rápida sea, minimizad los accesos a memoria. Acceder a un registro cuesta 1 o 2 ciclos de reloj. Acceder a memoria pueden llegar a ser cientos de ciclos. Así pues, es sencillo. Aprovechad los registros, y haced un poco de trileros con los registros para que los valores que hayamos leído una vez no los tengamos que leer otra vez.  Solo con esta optimización podéis llegar a multiplicar x10 o x20 el rendimiento de vuestra aplicación.

4. Usad trucos de asm: El lenguaje ensamblador lleva décadas usándose normalmente en plataformas muy justas de recursos. Esto ha llegado a desarrollar unos trucos que nos permitirán arañar ciclos de reloj cuando pensemos que ya no podemos hacer nada más.
 - Usad xor eax,eax  en vez de mov eax,0. Hace lo mismo y es más rápido. La operación xor de una variable con ella misma siempre es 0.
- Usad desplazamientos de bits en vez de multiplicaciones o divisiones con múltiplos de dos:
   shr ecx, 1   es lo mismo que ecx /= 2 pero muchísimo más rápido
   shl eax,2 es lo mismo que eax *= 4 pero muchisimo más rápido.

5. Usad instrucciones especificas para problemas específicos: Los compiladores de C ,normalmente, usan solamente unas 20 instrucciones de ensamblador para hacerlo todo. Esto es debido a la gran complejidad del proceso de compilación, pero realmente los procesadores poseen centenares de instrucciones. Ejemplos lo tenéis a cientos, una instrucción como bswap te permite hacer en un solo ciclo el trabajo que se podría hacer en decenas de ciclos. y las instrucciones de SSE te permiten trabajar con 4 ints a la vez aplicando funcionalidades que tal vez no habías ni imaginado. Tendréis que investigar pero ya veréis que con el suficiente tiempo dedicado podréis encontrar alguna instrucción que os resulte extremadamente útil en vuestra aplicación.

Y con esto acaban los consejos básicos para optimizar vuestro código ensamblador.

Espero que os haya gustado,

Nos vemos

LordPakusBlog

Tutorial de ensamblador x86. Capítulo 2. Conceptos básicos



Hola  a todos,

A partir de este capítulo espero que podáis hacer vuestras primeras funciones en ensamblador, y nos servirá de base para poder ir avanzando en conceptos un poco más peliagudos.

El código en ensamblador (como la mayoría de los lenguajes de programación) se basa en la ejecución de las instrucciones que queremos realizar sobre los datos que nos interesa modificar. Estos datos están almacenados en los registros.


Registros

Los registros son trozos  de memoria parecida a la RAM muy pequeña y muy muy rápida que están incrustados dentro del mismo procesador, por lo tanto, la forma de acceder y trabajar con ellos es particular de ese procesador.

Registros de 32 bits: Los registros de 32 bits son los más usuales y son los que nos permiten almacenar  1 int,  1 floats, 2 shorts o 4 chars.

Aunque hay bastantes registros internos, los que podemos usar desde programación son 6: eax, ebx, ecx, edx, esi, edi.

Aquí es donde se ha de ir con cuidado, hay registros con funcionalidades muy concretas y hay funciones que usaran ciertos registros sin previo aviso.

Ejemplos:
- La función de multiplicación acepta solo un parámetro, Este parámetro lo multiplicará por lo que haya en eax y el resultado lo dejará en eax y edx (la multiplicación de dos números de 32 bis puede ocupar 64 bits). Es decir, que después de usar la multiplicación eax y edx valdrán lo que ella quiera que valga no lo que nosotros teníamos almacenado.
- Las funciones de movimieno de string (movsd por ejemplo) usan de forma implicita los registros esi (source, origen) y edi (destination, destino) para apuntar a las zonas de memoria de origen y destino donde se hace la copia.
- La función ret (la que es comparable al return de C) no pide parámetros (al menos para el nivel que a nosotros nos interesa ), así que el valor de retorno de la función lo obtiene del registro eax de forma implicita.
- La función loop solo admite como parámetro el label de a donde tenemos que saltar. El contador de bucle es de forma implícita ecx.

Registros de 16 bits: A veces nos hace falta trabajar con 16 bits. Los registros de 16 bits de los que disponemos no son más que los 16 bits de menor peso de los registros anteriores, que son: ax, bx, cx, dx, si, di.

Registros de 8 bits: En ocasiones, 16 bits no es suficientemente pequeño y hemos usar registros del tamaño de 1 byte. Para estas ocasiones tenemos la opción de usar los bytes de los registros de 16 bits por separado: ax = al + ah, bx = bl + bh, cx = cl + ch, dx = dl + dh.

A nivel gráfico se podría resumir tal que así:



Operaciones:
Las instrucciones en ensamblador pueden aceptar un número variable de parámetros en función que operación sea (y en ocasiones una instrucción tiene diferentes usos en función de los parámetros que se le pasen)
Ya que hay cientos de funciones y todas ellas tienen sus particularidades vamos a explicar como funcionan poniendo ejemplos:

char LP_memcmp( const void * ptr1, const void * ptr2, size_t num )  //Cabecera de función en C
{
__asm   //Le decimos al compilador que vamos a iniciar un trozo de código en ensamblador
{
mov ecx,num //Movemos lo que tenemos en la variable num, dentro del registro ecx
mov esi,ptr1  //Movemos lo que tenemos en la variable ptr1, dentro del registro esi
mov edi,ptr2  //Movemos lo que tenemos en la variable ptr2, dentro del registro edi

....


no_multiplo:        //Etiqueta usada por las instrucciones de salto
dec ecx               // Decrementamos en una unidad el valor de ecx
mov al,[esi]        //Copiamos al subregistro  al el byte de memoria al que apunta el registro esi (es un                    puntero)

.....


cmp eax,ebx   //Compara el valor de eax con ebx
jg mayor          //Salta a la etiqueta "mayor" si eax es mayor que ebx
jl menor           //Salta a la etiqueta "menor" si eax es menor que ebx



Con todo lo que os he explicado y los ejemplos que he ido colgando deberíais ser capaces de empezar ha hacer vuestras primeras funciones. Para cualquier duda no dudéis en consultármela.

Nos vemos



LordPakusBlog




sábado, 24 de noviembre de 2012

Lista de instrucciones de ensamblador ASMx86 más usadas.

Artículo perteneciente al curso de ensamblador

Este artículo está íntimamente relacionado con "Referencias de programación"

Aquí os dejo las instrucciones de ensamblador más usuales en procesadores x86.

Movimiento de Datos:
mov: Movimiento de datos: Funciona entre dos registros o entre memoria y registro (y viceversa). Mueve un dato del tamaño del registro implicado de un lugar a otro. En C sería equivalente a la "instrucción" =. (a = b)
Ejemplos:
mov al,[esi]    mov ecx,num    mov eax, -1

bswap: Binary swap Invierte los bytes de un int. El primer byte lo intercambia con el cuarto y el segundo con el tercerto. Muy útil para trasnformaciones de litle-big endian.

shr: Shift right. Desplazamiento binario a la derecha de x posiciones. Equivalente en C a >>
shl: Shift left. Desplazamiento binario a la izquierda de x posiciones. Equivalente en C a <<


Operaciones lógicas:

and: Realiza la operación lógica "y" entre los dos operandos. Si queréis saber algo más de la operación lógica "y" podéis mirar este link

xor: Realiza la operación lógica "suma exclusiva" entre los dos operandos. Si queréis saber algo más de la operación lógica xor podéis mirar este link


Operaciones aritméticas:
inc:  Incrementa en un unidad el valor del registro que se le pase.(equivalente en C a ++)

dec:  Decrementa en un unidad el valor del registro que se le pase. (equivalente en C a --)

add: Suma dos registros y guarda el resultado en el primero. (equivalente en C a +=)

sub: Resta dos registros y guarda el resultado en el primero. ( equivalente en C a -=)


Comparaciones:
cmp: Compara dos registros y setea los flags para que después se puedan hacer saltos condicionales(jz, jnz, jg,etc...) (equivalente en C a if)

test: Aplica la operación and entre dos registros, pero sin poner el valor del resultado, solo preparando los flags para que después se pueden realizar los saltos condicionales.

jz: Jump Zero. Salto condicional. Salta a la etiqueta que se le diga si la comparación anterior daba 0.

jnz: Jump No Zero. Salto condicional. Salta a la etiqueta que se le diga si la comparación anterior daba diferente de 0.

jg: Jump Greater. Salto condicional. Salta a la etiqueta que se le diga si la comparación anterior daba que el primer elemento era mayor.

je: Jump Equal. Salto condicional. Salta a la etiqueta que se le diga si la comparación anterior daba que los dos registros eran iguales.

jne: Jump No Equal. Salto condicional. Salta a la etiqueta que se le diga si la comparación anterior daba que los dos registros no eran iguales.


Otras:
jmp: Operación de salto. La ejecución de código continua en la etiqueta que se le pase como parámetro. ( equivalente en C al poco recomendable goto)

loop: Mientras que en el registro ecx tenga un valor diferente de 0, decrementa ecx y salta a la etiqueta que se le diga. Muy util para realizar bucle. Equivalente en C a while( (ecx--) > 0 ) {}

leave: Limpia todo lo que hayamos tocado en la función en la que estamos para que podamos volver a la  función que nos ha llamado sin afectar al programa. Siempre se llama antes de hacer un ret

ret: Retorno de función. Equivalente al return de C




 A medida que vaya desarrollando el curso y poniendo más ejemplos iré rellenando esta lista. Si creéis que falta alguna instrucción importante decirlo.

P.D: Si quereis también podeis mirar la siguiente lista que os podrá ayudar en vuestro estudio del ensamblador

LordPakusBlog


Tutorial de ensamblador x86. Capítulo 1. Introducción al ensamblador



Hola a todos,

Bienvenidos a este primer capítulo del tutorial de ensamblador. Antes que nada, deberíamos ponernos en situación

Que es el ensamblador?
El lenguaje ensamblador es el lenguaje de programación más parecido al que utiliza nuestro procesador, permitiéndonos ejecutar programas en  dicho procesador como nosotros queremos.
Los diferentes lenguajes  compilados ( C,C++, Fortran, etc...) realmente lo que hacen es pasarse a ensamblador para que el compilador pueda ejecutarlos.

Por que usar ensamblador?
El motivo más claro para usar ensamblador es el aumento de rendimiento de nuestras aplicaciones. Al usar las instrucciones de ensamblador como nosotros queremos podemos desarrollar códigos óptimos que realicen tareas varios ordenes de magnitud más rápidas que con lenguajes compilados. Esto es así por que los compiladores (los programas que se encargan de "traducir" código C ,por ejemplo) a ensamblador no son perfectos, ni pueden serlo debido a la alta complejidad de las casuísticas posibles y usan los recursos de nuestro procesador de forma subóptima.

Por que no se usa más el ensamblador?
Si el ensamblador es tan bueno, se hace complicado entender por que no se usa en todos los ámbitos de la programación, la respuesta es sencilla: la complejidad. El código escrito en ensamblador tranquilamente es 10 (o 100 ) veces más largo en extensión de lineas de código que un programa en C, aparte de que es más complejo entenderlo, debugarlo y en general desarrollar sobre este lenguaje directamente.

Como se puede usar el lenguaje ensamblador?
Hay dos maneras básicas de usar el ensamblador, una es usando directamente un programa ensamblador que nos pase nuestro código .asm a .exe o .o o lo que toque (como un compilador de C/C++). La otra manera (que es la única que uso y que explicaré) es mediante ensamblador inline. La mayoria de los compiladores actuales permiten introducir trozos de ensamblador dentro del código C/C++, permitiendo usar el ensamblador solamente para aquellas funcionalidades que realmente comprometen el rendimiento total de la aplicación. Es una manera limpia y bastante mantenible de introducir ensamblador en nuestros programas.

Como introduzco código ensamblador en mis programas de C?
He aquí el gran problema del ensamblador inline, no está estandarizado. Esto quiere decir que cada compilador tiene potestad para hacer un poco lo que quiera. Gcc usa un estandar que no me gusta demasiado(algo malo debería tener :D ) ,  DevCpp lo mediosigue, WatCom (un compilador bastante antiguo) tiene su forma muy particular de decidir que registros se pueden modificar o no y de linkar los parametros de entrada de las funciones con los registros del compilador, pero sin duda, para mi el mejor sistema de ensamblador inline es el MSVC++ (lo podéis encontrar en versión gratuita). Este sistema es el que usaré para todos los ejemplos. Si alguien tiene problemas en su compilador que me lo diga y le buscaremos una solución.

La forma de introducir código ensamblador inline es visual studio (MSVC) es tan tonta como la siguiente (solo es un ejemplo):

Código en C


__asm
{
mov ecx,num
mov edi,destination
mov esi,source

shr ecx,2

rep movsd

leave
ret
}


Código en C

En breve os pasaré una lista con las instrucciones más típicas y empezaremos el curso de ensamblador como tal.

Espero que os guste.


Nos vemos


LordPakusBlog

domingo, 18 de noviembre de 2012

LP_memDif. Fast memdif: memdif rápido en asm x86

Articulo íntimamente relacionado con el curso de ensamblador

Este artículo proviene de una mejora del memcmp en ensamblador

Hola a todos,

Hay bastantes ocasiones en que usamos el memcmp solamente para saber si dos trozos de memoria son iguales o diferentes , sin importarnos demasiado en que bit se diferencian ni ningún tipo de información adicional.

Es por ello que he desarrollado esta nueva funcionalidad memDif  basándome en mi implementación en asm del memcmp.

Probadlo a ver como os va, la filosofia es la misma que la del memcmp: 0- los dos trozos de memoria son iguales, otro valor- son diferentes.

A nivel de rendimiento va el doble de rápido que el memcmp de C estándar (que no es poco) y espero que os pueda servir en vuestras aplicaciones cuyo rendimiento dependa de la comparación de zonas de memoria.

Aquí os dejo el código:


char LP_memDif( const void * ptr1, const void * ptr2, size_t num )
{
__asm
{
mov ecx,num
mov esi,ptr1
mov edi,ptr2

test ecx,3
jz multiplo4

xor eax,eax
xor ebx,ebx
no_multiplo:
dec ecx
mov al,[esi]
mov bl,[edi]
xor eax,ebx
jnz fin
inc esi
inc edi

and ecx,ecx
jz fin

test ecx,3
jnz no_multiplo

multiplo4:
shr ecx,2

bucle:
mov eax,[esi]
mov ebx,[edi]
add esi,4
add edi,4

xor eax,ebx
jnz fin
    loop bucle

fin:
leave
ret

}
}

Espero que os haya gustado.

Nos vemos


LordPakusBlog

LP_memcmp. Fast memcmp: memcmp rápido en asm x86

Articulo íntimamente relacionado con el curso de ensamblador

Hola a todos,

La libreria stdlib.h de C da muchas funcionalidades con un rendimiento impresionante (memcmp y memcpy tiene una velocidad espectacular), pero incluso así son mejorables y en algunos casos las características de nuestro software nos obligan a optimizarlas.

La implementación del memcmp de C está hecha byte a byte por temas de compatibilidad con las diferentes plataformas existentes, pero realmente, si optimizamos para la plataforma x86 (que es de largo la más usada en los hogares de todo el mundo) podemos obtener grandes beneficios de usar 32 bits en vez de 8.

En nuestro caso concreto , los 32 bits se usan para poder comparar 4 bytes a la vez, reduciendo los accesos a memoria y el tiempo de cálculo. La interfície de parámetros es exactamente la misma que la que se usa para el memcmp de stdlib, así que podréis substituir rápidamente las llamadas para probar su funcionamiento.

Espero que os guste:



char LP_memcmp( const void * ptr1, const void * ptr2, size_t num )
{
__asm
{
mov ecx,num
mov esi,ptr1
mov edi,ptr2

test ecx,3
jz multiplo4

xor eax,eax
xor ebx,ebx

no_multiplo:
dec ecx
mov al,[esi]
mov bl,[edi]
inc esi
inc edi
cmp eax,ebx
jg mayor
jl menor

and ecx,ecx
jz igual

test ecx,3
jnz no_multiplo

multiplo4:
shr ecx,2
bucle:
mov eax,[esi]
mov ebx,[edi]

bswap eax
bswap ebx

add esi,4
add edi,4

cmp eax,ebx

jg mayor
jl menor
loop bucle

igual:
xor eax,eax
leave
ret
mayor:
mov eax,1
leave
ret

menor:
mov eax, -1
leave
ret
}
}



 La función ha sido probada para bastantes casos y parece que su funcionalidad es la correcta y el tiempo de ejecución es un 25% más rápido. 

Si este rendimiento no os es suficiente siempre podéis mirar el siguiente link para conseguir algo más de  rendimiento en vuestro memcmp

A ver si os animáis a probarla en vuestras aplicaciones y me contáis que mejora de velocidad observáis.

Nos vemos.

LordPakusBlog

domingo, 11 de noviembre de 2012

Datahsheets Familia Lógica 74XX


Hola a todos,

Cuando empecé a jugar con la electrónica internet  no estaba ni en pañales practicamente, así que acceder a la información de los componentes era bastante complicado. Como compraríais componentes electrónicos sino sabéis ni como pedirlos?? No podéis ir  a la tienda de electrónica y pedir 100 gramos de puertas AND ni media docena de puertas XOR. De hecho, no puedes pedir ni tan siquiera "un chip con 4 OR's de 2 entradas". Para todo ello se ha de usar la numeración de los chips que nos proporciona el fabricante.

Hay muchos fabricante de chips, los que os pongo a continuación son solamente los que mas accesible tienen su web.

Fabricantes:
FAIRCHILD Semiconductor
Texas Instruments
ST Microelectronics
ON Semiconductor

Si accedéis a estas webs y buscáis un poco encontrareis los datasheets. Un datasheet no es más que un pdf que nos da el fabricante con toda la información que el considera necesaria para que nosotros podamos diseñar nuestro circuito y después comparle los chips. Es bastante útil que vayáis leyendo alguno si nunca lo habéis hecho.

Aparte de eso, hay un estándar en cuanto a la numeración de chips de puertas lógicas, lo que se conoce como familias lógicas. En nuestro caso nos centraremos en la familia 74XX.  Vereis que para cada modelo de puerta hay un número genérico para todas (7400 por ejemplo), pero que después cada fabricante le incluye todos los identificadores que considera necesarios para diferenciar sus características respecto a la competencia.

Os dejo la lista de datasheets:

7400: Puertas NAND de 2 entradas.
FairChild
Texas Instruments
ST Microelectronics
ON Semiconductor

7408: Puertas AND de 2 entradas
FairChild   
Texas Instruments
ST Microelectronics
ON Semiconductor  

7432: Puertas OR de 2 entradas
FairChild    
Texas Instruments
ST Microelectronics
ON Semiconductor

7402: Puertas NOR de 2 entradas
FairChild
Texas Instruments
ST Microelectronics

7486: Puertas XOR de 2 entradas
FairChild   
Texas Instruments
ST Microelectronics
ON Semiconductor

74266: Puertas XNOR de 2 entradas
Texas Instruments


Si algún fabricante no aparece para algún modelo es que o bien no he sabido encontrar el datasheet  o bien ese fabricante no produce ese modelo.

Espero que os sirva, para cualquier duda ya lo sabeís.

Nos vemos

LordPakusBlog

sábado, 10 de noviembre de 2012

Curso de electrónica: Capitulo 4. Circuitos digitales con memoria




Hola a todos...

Hasta ahora todo lo que os he enseñado son circuitos que dada una entrada, dan una salida, pero como todos comprenderéis con esto no se podría hacer un PC.

Para realizar circuitos de dimensiones grandes o de una cierta complejidad se ha de tener un mínimo de capacidad de recordar que ha pasado antes.

Los circuitos que almacenan información más usuales son:

Contadores
Un contador no es más que un dispositivo que se encarga de ir contando cuantos pulsos a tenido en su entrada. En principio no tendría mucha más utilidad que la que podría tener en algún que otro circuito concreto pero realmente es de gran utilidad en los procesadores. Cuando llegue el momento ya os lo explicaré con más precisión.

El esquema de un contador es más o menos el siguiente:
Vcc y GND: alimentación del circuito
A,B,C y D son el valor inicial con el que queremos cargar el contador
Load: Es la señal que indica que se ha de cargar A,B,C y D dentro del contador
Clear : Es la señal que indica que el contador se ha de poner a 0.
Carry y Borrow: Se usan para conectar diferentes contadores en cascada.
Qa,Qb,Qc y Qd son lel valor de cuanto vale el conador en ese momento.
Down y Up: Son las señales para incrementar o decrementar el valor del contador.

Memorias
Como todo el mundo sabe, las memorias son dispositivos para almacenar datos. Se pueden implementar mediante puertas lógicas, pero actualmente se desarrollan directamente con 1 transistor (es complicado de explicar), así que no tiene mucho sentido que le demos vueltas a como están hechas sino a cual es su funcionamiento.
Una memoria típica tiene las siguientes conexiones:

- Una entrada para habilitarnos el funcionamiento del chip (CE: Chip Enable o CS: Chip Select)
- Otra para decirnos si vamos a leer o escribir (R/W)
- Opcionalmente pueden tener una entrada para permitir la salida de datos (OE: Output enable). normalmente es donde conectaremos el reloj del sistema para tener nuestro diseño sincronizado.
- Un conjunto de bits de entrada sirven para decir en que dirección de memoria vamos a guardar o leer el valor en concreto que nos interesa (A0..A13)
- Un  conjunto de bits que conformarán el valor que vamos a grabar en memoria (si ello es necesario) (D0..D7)
- En estos bits D0..D7 nos devolverá el valor almacenado en la dirección que le hayamos proporcionado si era una operación de lectura.

A este esquema de funcionamiento responde tanto la RAM de nuestro PC como los registros internos de nuestro procesador

Espero que os haya gustado. En breves capítulos comenzaremos a unir conceptos de los diferentes capítulos para que veáis todo lo que se puede hacer.

Nos vemos

LordPakusBlog

viernes, 2 de noviembre de 2012

Macros predefinidas estándar de C

Este artículo pertenece a Referencias de programación

Está intimamente ligado a éste articulo

Las macros predefinidas por el compilador son un conjunto de defines usables en nuestros programas que nos proporcionan información interesante del entorno y del momento de compilación o bien de características del mismo código.

Aquí tenéis la lista de macros:

Macros estandar:

__FILE__ : Nos dice en que fichero de código estamos.
__LINE__ : Nos dice en que linea del código estamos.
__DATE__: Nos dice la fecha en la que se realizó la compilación
__TIME__: Nos dice la hora a la que se realizó la compilación.
__TIMESTAMP__: Nos dice la fecha y la hora de la compilación.

Macros no estandar (funcionan en gcc y algunas en MSVC):
__FUNCTION__ : Nos dice en que función estamos.  __func__ existe en C99.
__STDC__ : Nos dice si el compilador es estandar o no.
__STDC_VERSION__: Nos dice la versión del compilador.
__STDC_HOSTED__: El compilador cumple con todas las librerias estandar.
__cplusplus : El compilador acepta c++
__OBJC__ : El compilador acepta objective-c
__ASSEMBLER__ : El compilador acepta ensamblador.

Macros de VisualStudio:
_ATL_VER : Versión de ATL
_CHAR_UNSIGNED : Nos dice si el tipo char es unsigned por defecto o no.
__COUNTER__ : Contador de compilaciones
_DEBUG: Se activa si estamos compilando en modo debug
__FUNCTION__ ,__FUNCDNAME__, __FUNCSIG__ : Nos dan información de la función desde donde llamamos a la macro.
_WIN32 : Está definido para aplicaciones de 32 y 64 bits
_WIN64 : Está definido para aplicaciones de 64 bits.

Hay algunas macros que no he puesto ya que son complejas de entender, de explicar y de usar, pero su aplicación real es anecdótica, así que no nos debería afectar.

Espero que os haya gustado.

Nos vemos




jueves, 1 de noviembre de 2012

Tutorial de programación C/C++ desde 0. Capitulo 12

Hola a todos,

Si habéis estado siguiendo todo el tutorial ya sois capaces de entender código y de comenzar a picar vuestros programas , cada vez un poco más complejos.

Así pues, os puede comenzar ha hacer falta alguna manera para tracear vuestro código y encontrar los fallos que os vayan surgiendo. Para ello usaremos las "macros estandar predefiinidas", es decir, defines que podemos usar para dar más funcionalidad al código.

Aquí os dejo el código de ejemplo, es bastante sencillo:



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

#define TRAZA printf("%s %d\n",__FILE__,__LINE__)
void function2(int b)
{
TRAZA;
printf("Funcion2: %d\n",b);
}

void function1(int a)
{
TRAZA;
printf("Funcion1: %d\n",a);
}

void main()
{
function1(3);
function2(5);
system("pause");
}

Y aquí una foto del resultado:

Las macros __FILE__ y __LINE__ las rellena el precompilador antes de generarse la compilación, es decir, nos substituye estas macros por el fichero y la linea en la que nos encontramos en ese momento.

Esto es extremadamente útil si queremos realizar alguna funcionalidad de debug (buscar errores) o profiling (mejorar velocidad).

La macro que nos pone  la función en la que  estamos, sería todavía más útil  pero no es estándar para todos los compiladores, así que no lo pondré aquí.

Espero que os haya gustado

Nos vemos

LordPakusBlog

Entradas populares