Tips de lenguaje C

Hola chicos se me ha ocurrido la idea de: ¿Que tal un post donde estén trucos y tips para mejorar la eficiencia?. Esto debido a que c genera mucho código innecesario y mi lema es "Es mejor un C eficiente y ordenado, que un ASM deficiente e incompresible".

Así que, ¿que truco conocen para ahorrar memoria y ser mas eficiente?

Comienzo yo :D:

CASO 1:
En vez de:
variable = variable * (2 o 4 o 8 o 16)
usar:
variable = variable << (1 o 2 o 3 o 4 respectivamente)

En vez de:
variable = variable / (2 o 4 o 8 o 16)
usar:
variable = variable >> (1 o 2 o 3 o 4 respectivamente)

CASO 2:
Si tienes una estructura con muchas variables ni se te ocurra pasarla como parámetro (yo tenia una con 12 variables) y cada vez que llamaba a la función pasándole la estructura era un 1% menos de memoria ROM, es mejor declarar de manera global (No se mucho de ASM pero creo que esto es debido a que coloca cada variable en la pila antes de llamar la función).

CASO 3:
y por ejemplo yo tenia que hacer una comprobación si era TRUE mostrar " "(espacio) y si era FALSE mostrar la "B"
en vez de
Código:
int1 esModificable = TRUE;
if(esModificable == TRUE){
[INDENT]esModificable = FALSE;
lcd_putc("B");[/INDENT]
}else{
[INDENT]esModificable = TRUE;
lcd_putc(" ");[/INDENT]
}
use:

Código:
int8 esModificable = ' ';
isModificable = isModificable ^ 98;		// Cambia entre el caracter " " y el "B" para evitar if.
lcd_putc(isModificable);
Si se dan cuenta uso un circunflejo este es el operador XOR en binario la:
01000010 = 66 (que en ascii es "B")
y
00100000 = 32 (que en ascii es " ")
si le aplico un xor con el numero 98
01100010 = 98
01000010 = 66
desde la izquierda tomando el binario de 66 y 98:
0 y 0 = 0
1 y 1 = 0
0 y 1 = 1
0 y 0 = 0
0 y 0 = 0
0 y 0 = 0
1 y 1 = 0
0 y 0 = 0

Sip es lo que ustedes piensan da 00100000 que es igual a 32 y si le aplican de nuevo se invierte.
Si ven es mas eficiente genera menos codigo yo ahorre un 0.2% de espacio (si se que no es mucho) pero si les falta un poco pueden aplica mini optimizaciones como esta y escribir lo demas en C y no usar la dificultad de asm.

Espero que les haya gustado (y me hayan entendido) espero sus consejos y trucos :).

Como extra les dejo esta pagina que contiene mas sobre formas de optimizar http://www.ual.es/~jjfdez/IC/Practicas/optim.html.

Por cierto no estoy seguro si colocarlo aquí o en interfaz y programación xd
 
Última edición:
Fíjate que hay un método para usar sólo registros de corrimiento en lugar de desperdiciar preciosos pines del microcontrolador.
Podemos usar el 74HC595 y el CD4021 para leer o escribir en el puerto.

La idea es simple, sólo enviamos un pulso de reloj por cada dato y completados los 8 bits, el puerto está listo.
Así podemos usar motores paso a paso, LCD, DAC y todo a 3 pines, algo muy similar al SPI.

En cuanto tenga tiempo subiré el algoritmo. ;)
 
Última edición por un moderador:
Ese lo estoy usando casualmente xd con el 74ls164
este es el que uso:
Código:
#define enable PIN_D0
#define CLOCK PIN_D4
#define DATO PIN_D3

#define PIN_OFF output_low
#define PIN_ON output_high

#define set_tris_lcd(x) set_tris_b(x)

#define LCD_TYPE 2           // 0=5x7, 1=5x10, 2=2 lines
#define LDC_LINE_TWO 0x40    // LCD RAM address for the second line

BYTE const LCD_INIT_STRING[4] = {0x20 | (lcd_type << 2), 0xf, 1, 7};
// These bytes need to be sent to the LCD
// to start it up.

void lcd_send_nibble( BYTE n ,BYTE rs) {
	int8 d,q;  
	d=8;

	for(q=0;q<4;q++){
		pin_off(CLOCK);
		output_bit(DATO,(n&d));   
		d = d >> 1;
		pin_on(CLOCK);
	}
	pin_off(CLOCK);
	output_bit(DATO,rs); 
	pin_on(CLOCK);
	pin_on(enable);
	delay_us(30);
	pin_off(enable);  
}


void lcd_send_byte( BYTE address, BYTE n ) {

	delay_cycles(1);
	pin_off(enable);

	lcd_send_nibble((n >> 4),address);
	lcd_send_nibble((n & 0xf),address);
}


void lcd_init() {
	BYTE i;
	pin_off(enable);
	delay_ms(15);
	for(i=1;i<=3;++i) {
		lcd_send_nibble(3,0);
		delay_ms(5);   
	}      
	lcd_send_nibble(2,0);
	for(i=0;i<=3;++i)
		lcd_send_byte(0,LCD_INIT_STRING[i]);
}

void lcd_gotoxy(BYTE x, BYTE y){
	BYTE address;

	if(y!=1)
		address=LDC_LINE_TWO;
	else
		address=0;

	address+=x-1;
	lcd_send_byte(0,0x80|address);
}

void lcd_putc(char c){
	switch (c){
		case '\f':
			lcd_send_byte(0,1);
			delay_ms(2);
			break;
		case '\n': 
			lcd_gotoxy(1,2);
			break;
		case '\b': 
			lcd_send_byte(0,0x10); 
			break;
		default: 
			lcd_send_byte(1,c);
			break;
	}
}
void lcd_clear(int1 line){
	int8 i;
	for(i = 1; i <= 16; i++){
		lcd_gotoxy(i, line + 1);
		lcd_send_byte(1,' ');
	}
	lcd_gotoxy(1, line + 1);
}
 
Última edición:
Yo uso una muuucho mas simple y confiable: ajusto la optimizacion del compilador para lo que necesito (minimizar espacio, maximizar velocidad... lo que sea) y lo dejo hacer su trabajo. Si el resultado no me satisface, recien entonces analizo el problema.

De las "recetas" que mencionan se desconoce la validez y el campo de aplicacion, por que cada compilador puede generar un codigo potencialmente diferente y los "ahorros" pueden terminar siendo nulos...

Ademas, la optimizacion de espacio es un problema completamente colateral si lo que se busca es velocidad de ejecucion... y no me digan que "poco codigo se ejecuta rapido" por eso es falso, y si no me creen googleen por "loop unrolling".
 
Yo uso una muuucho mas simple y confiable: ajusto la optimizacion del compilador para lo que necesito (minimizar espacio, maximizar velocidad... lo que sea) y lo dejo hacer su trabajo. Si el resultado no me satisface, recien entonces analizo el problema.

De las "recetas" que mencionan se desconoce la validez y el campo de aplicacion, por que cada compilador puede generar un codigo potencialmente diferente y los "ahorros" pueden terminar siendo nulos...

Ademas, la optimizacion de espacio es un problema completamente colateral si lo que se busca es velocidad de ejecucion... y no me digan que "poco codigo se ejecuta rapido" por eso es falso, y si no me creen googleen por "loop unrolling".

Ahh eso claro no es lo mismo hacer varias multiplicaciones (con una función por ejemplo) que ejecutarlas inline la cual tomara mas espacio pero no hace llamadas o saltos los cuales son mas lentos. A lo que me refiero es de cualquier tipo y la persona lo tome segun a sus necesidades por ejemplo TRILO-BYTE dijo que hacer uso del printf ocupa bastante espacio algo que no sabia. Yo me refiero mas a buenos hábitos o cosas que permitan una minimejora que al acumularse haga una diferencia, también dije para hacerlo mejor ordenado ya que no es lo mismo usar if-else que un switch o un while que un for (claro tampoco cosas tan basicas pero si que pasen a veces desapercibidas) si es por ahorrar espacio se puede usar recursividad pero si se quiere velocidad no se puede usar no ando buscando la solucion "divina" xd si no algo que podamos aplicar en determinado caso.
Por cierto el compilador es "bruto" puede ser muy optimizado pero no puede sustituir a un buen programador (gracias a Dios) por que si no no se necesitarían de estos ultimos (hasta que llegue el momento de que las maquinas piensen xd)
 
Ahí no acaba la historia:
La generación de código del compilador puede ser dependiente del contexto, y es casi imposible saber el código que se va a generar por que el compilador va usando y reusando registros y variables transitorias en memoria. Por eso opino que intentar aplicar "reglas de codificación optimizada a mano" no es algo muy rentable, por que terminan alterando el comportamiento normal del compilador para generar código en un contexto diferente a lo que sería normal.

Por otra parte, ahorrar memoria solo por el gusto de hacerlo y sin tener una necesidad real de ello, no conduce a nada bueno, en particular a la legibilidad y mantenimiento del código.

Digo... si vas a programar un proyecto para vos y si no sirve o no funciona solo perdés unos pocos dólares, está todo bien. Pero en una empresa que diseña productos para un mercado masivo y luego tiene que darle soporte, la forma de trabajar es diferente.... y no es bueno traer "mañas" aprendidas en experimentos caseros.
 
No son "mañas" y no es por el gusto.
Como dije en el post, si te falta espacio (un 3% o 5%) puedes aplicar estas mini mejoras y no gastar más en un PIC con más memoria.
Y no trabajo para desarrollar el firmware de un PIC, pero comúnmente en una PC en el caso de un juego o aplicación de diseño, no se valieran de optimización del compilador ni de "mañas" sino que lo escribirían en ASM.
Pero como no me pagan por eso sino que es un hobby, me gustaría conocer "mañas" que en realidad son características para hacer mi código mejor.

Por ejemplo, yo empecé hace poco con los PIC y venía con un fuerte aprendizaje del principio del menor privilegio, algo que en PIC lo veo innecesario y por hacer variables y pasarlas por funciones en vez de globales, me cobraba con la ROM.
Si hubiera visto un post de "Tips de C" y me hubieran dicho que era mejor rotar o usar variables globales, me hubiera evitado eso... digo.

Por cierto... Otro truco:

Si quieres ahorrar espacio (a veces no siempre) en PIC C de CCS, puedes usar la directiva #SEPARATE antes de la función, lo cual ahorrará memoria ROM pero pierdes velocidad (si el compilador va hacia inline) y usa más espacio de la pila, o puedes usar #inline si no te importa la ROM sino más la velocidad.
(A veces el compilador las coloca #inline, otras no, por eso lo de hacerlo explicito).
 
Última edición por un moderador:
Con el caso 2 mucho que digamos no estoy de acuerdo. Si se puede evitar la variable global, mejor.

Para trabajar con estructuras que van de una función a otra, para no tener una "copia" de esa estructura al pasarla, lo que se debe hacer es trabajarla por referencia, es decir pasarle a la función la posición de memoria donde está alojada la variable de la estructura, de esa forma siempre trabajás con una sola estructura.

La ventaja de trabajar por referencia, es que no todo el código puede ver esa variable de estructura y solo pueden verla aquellas funciones donde explícitamente querías que la estructura se vea.
 
Con el caso 2 mucho que digamos no estoy de acuerdo. Si se puede evitar la variable global, mejor.

Para trabajar con estructuras que van de una función a otra, para no tener una "copia" de esa estructura al pasarla, lo que se debe hacer es trabajarla por referencia, es decir pasarle a la función la posición de memoria donde está alojada la variable de la estructura, de esa forma siempre trabajás con una sola estructura.

La ventaja de trabajar por referencia, es que no todo el código puede ver esa variable de estructura y solo pueden verla aquellas funciones donde explícitamente querías que la estructura se vea.

A eso me refería con el principio del menor privilegio (Las funciones deben ser capaz de acceder a la mínima cantidad de información necesaria para su correcta ejecución) sin embargo es para mejorar la seguridad y en programación defensiva no entiendo su uso en los pic. respecto a lo de pasarlo por referencia eso ahorra RAM no la memoria ROM claro que si no se hace global también se ahorra RAM al haber solo las variables necesarias, por eso solo hago global estructuras que usan muchas variables. Aunque supongo que si se le pasa la dirección de memoria no necesita cargar variables en la pila, así que también ahorraría ROM no se tendría que probar y ver si se obtienen los mismos resultados, no se me paso por la mente que quizás con la referencia no tenga que cargar cada variable si no que apunta en el inicio de la estructura... déjame pruebo a ver.. (sinceramente odio usar variables globales no dejan ver de donde salieron y a mi parecer hacen menos entendible el código).
 
El paso de estructura como parametros de una funcion implica la copia de todos los bytes del parametro actual en los del formal ya que en C el paso siempre es por valor.
En cambio si pasas un puntero tambien se copia pero solo 2 o 4 bytes de tamaño del puntero.
Lo mismo sucede si la funcion devuelve una estructura o una referencia a la misma.
Lo que propone Cosme es la solucion correcta.
 
Nop error el caso 2 si tiene valides prueba de uno que estoy programando orita diferencia entre global y por referencia.. Es de un 2,9% y solo cambie entre tenerla global o por referencia. Hice 8 veces la llamada a la misma función y hubo un incremento del 5% que suma un total de 7,9%... hice 21!! llamadas en global y no incremento ni 1% (solo 0,5%).
 

Adjuntos

  • diferencia.jpg
    diferencia.jpg
    17.9 KB · Visitas: 19
Última edición:
tengo una estructura
con
Código:
	int16 resolution;				//	Resolucion del ciclo de trabajo.
	int16 duty;
	int16 pr2, pr2Ant;				//	PR2 y la variable que almacena el anterior.
	int8 posHZ;						//	Posicion en el arreglo hz.
	int8 cicloTrabajo;				//	Almacena ciclo de trabajo.
	int8 pScale;					//	Preescaler
	int8 limite;					//	Maximo que se puede editar ciclo de trabajo.
cree una variable global con la estructura
Código:
Pwm pwm;  //(uso typedef y #case)
llame a una función llamada setFrecuencia modificando pwm, primero global y después usando setFrecuencia(pwm) para modificarla. (obviamente en la declaración de la función coloque Pwm &pwm).

Cuando llamo por referencia "setFrecuencia(pwm)" ocupa alrededor de un 0,3% cada vez que la llamo.

Si lo hago global "setFrecuencia()" ocupa cada vez que la llamo un 0,023%.
cuando lo hago por referencia es desde una función llamada "generadorPwm()". ahi esta el .c (cualquier sugerencia para mejorarlo estoy abierto a opiniones xd ademas no esta completo xd).
 

Adjuntos

  • Pwm.zip
    1.7 KB · Visitas: 10
Última edición:
Una vez diseñé un taxímetro para hacer mi propia marca, cumplía todas las normas y estaba basado en un taxímetro comercial marca BLUE-H, un taxímetro mexicano.

En realidad superaba a éste taxímetro que estaba hecho con 6 displays de 7 segmentos, 4 botones, entrada de velocímetro, un buzzer, un calendario DS1320 y un PIC16F886.

Nunca supe en que estaba programado tal PIC, pero hice mi propio firmware clónico.
Lo malo que para pasar las pruebas de laboratorio, las pruebas son muy caras y piden mínimo 1000 piezas fabricadas, así que decliné a la idea del taxímetro.

Pero con lo que si me topé, fue que mi horrenda manera de programar se había consumido la ROM del microcontrolador.

Ahí fue donde me vi obligado a economizar instrucciones y me dí cuenta que varios If ocupan mas RAM y ciclos de trabajo del CPU, y ahí vi que era útil usar Switch.

Luego me dí cuenta que a veces las funciones ocupan mas memoria, tanto RAM como ROM, ahí estaba comprobando y comprobando lo que generaba el .LST del compilador.

Aunque mi adorado taxímetro no pudo ser comercializado, me ayudó a aprender a exprimir cada byte de la ROM y de la RAM.
 
Última edición por un moderador:
Es interesante el tema. Los tips que se mencionarán podrían ayudarnos a hacer mejor los códigos.
Vamos a publicar las optimizaciones que sabemos, para aplicarlas en proyectos que más adelante tengamos.

Por ahora pongo mi granito de arena:
PHP:
typestruct Info{

    unsigned char* Name;
    int age;
    unsigned char* address;

};

Info Datos;


void main(void){

        Datos.Name = "Forosdeelectronica";
        Datos.age = 17;
        Datos.Address = "Internet";


}
Este código es simple, es para poder crear una estructura de datos y pasarlos en varias funciones que tengamos.
PHP:
void mifuncion(Info Datos2){

       Datos2.Name = "MyName";
       Datos2.age = 256;
       Datos2.Address = "google.com";

}
 
Última edición por un moderador:
Desde linux usando GCC, tengo este código:

PHP:
#include <stdio.h>

//#define REFERENCIA

struct Prueba
{
    unsigned int valor1;
    unsigned int valor2;
};

#if defined(REFERENCIA)
    void ModificarValor(struct Prueba *variable_estructura)
    {
        int aux;

        scanf("%d", &aux);
        variable_estructura->valor1=aux;

        scanf("%d", &aux);
        variable_estructura->valor2=aux;
    }
#else
    struct Prueba variable_estructura;

    void ModificarValor()
    {
        int aux;

        scanf("%d", &aux);
        variable_estructura.valor1=aux;

        scanf("%d", &aux);
        variable_estructura.valor2=aux;
    }
#endif


int main()
{
    #if defined(REFERENCIA)
        struct Prueba variable_estructura;
        ModificarValor(&variable_estructura);
    #else
        ModificarValor();
    #endif

    printf("Valor del entero 1: %d\nValor del entero 2: %d\n", variable_estructura.valor1, variable_estructura.valor2);

    return 0;
}

Cuyo peso final en el Release dá 6344 bytes usando el flag "-O2".

Definiendo "REFERENCIA", ahora se trabaja las estructuras por referencia, el resultado del código es exactamente igual a 6344 bytes usando el flag "-O2".

Cabe aclarar que los #if solo sirven para habilitar o deshabilitar parte del código, es decir que aquellas rutinas que no están dentro de la condición, para el compilador directamente no existen.
 

Adjuntos

  • Prueba.zip
    12.8 KB · Visitas: 6
Última edición:
Eso es muy interesante. Hay quienes usan muchas directivas del preprocesador para habilitar o deshabilitar código.

En muchos ejemplos se puede ver cuando se trabaja con microcontroladores de diferentes familias, pero el código es genérico.
Donde todos los parámetros de configuración de un microcontrolador, digamos, FULANO, están dentro de un #if

PHP:
#if defined(FULANO)
//oscilador
//pines
//etc
#endif   

//si no estamos trabajando con FULANO
#elif defined(PERENGANO) 
//oscilador
//pines
//etc
#endif [/B]
También para preguntar en que compilador estamos trabajando.

Ejemplo:
PHP:
#if defined( C_fulano1)
// configuraciones propias del compilador   1
    
#endif

#if defined( C_fulano2)
// configuraciones propias del compilador   2
    
#endif
 
Última edición por un moderador:
Desde linux usando GCC, tengo este código:

PHP:
#include <stdio.h>

//#define REFERENCIA

struct Prueba
{
    unsigned int valor1;
    unsigned int valor2;
};

#if defined(REFERENCIA)
    void ModificarValor(struct Prueba *variable_estructura)
    {
        int aux;

        scanf("%d", &aux);
        variable_estructura->valor1=aux;

        scanf("%d", &aux);
        variable_estructura->valor2=aux;
    }
#else
    struct Prueba variable_estructura;

    void ModificarValor()
    {
        int aux;

        scanf("%d", &aux);
        variable_estructura.valor1=aux;

        scanf("%d", &aux);
        variable_estructura.valor2=aux;
    }
#endif


int main()
{
    #if defined(REFERENCIA)
        struct Prueba variable_estructura;
        ModificarValor(&variable_estructura);
    #else
        ModificarValor();
    #endif

    printf("Valor del entero 1: %d\nValor del entero 2: %d\n", variable_estructura.valor1, variable_estructura.valor2);

    return 0;
}

Cuyo peso final en el Release dá 6344 bytes usando el flag "-O2".

Definiendo "REFERENCIA", ahora se trabaja las estructuras por referencia, el resultado del código es exactamente igual a 6344 bytes usando el flag "-O2".

Cabe aclarar que los #if solo sirven para habilitar o deshabilitar parte del código, es decir que aquellas rutinas que no están dentro de la condición, para el compilador directamente no existen.

Hola cosmefulanito04 te equivocas quizás estas viendo el tamaño en disco y no el tamaño Cómo tal recuerda que el disco duro se divide en clusters y si el cluster es de 40kb y el archivo ocupa 1kb en disco ocupa 40 derrochando los otros 39 :( por eso no ves cambio debido a que en disco ocupan igual :cry: dame un chance cuando tenga tiempo subo uno que hice igual que tu un pic 18f4550 con ccs en global ocupó 0.5% :D y el lst era poco mas de 200 lineas en cambio por referencia son más de 2000 y ocupan casi un 10% :eek: luego realice la prueba en visual studio con visual c++ y ocupa el archivo obj casi 300 bytes mas (claro para una pc no es nada si se toma la seguridad que representa el ocultamiento de información) pero un pic no necesita esa seguridad, necesita esa memoria luego mando un zip con el archivo c y una imagen comparativa.

Ahi deje el archivo abrelo y compilalo te daras cuenta de la diferencia (Aunque exagere llamando 30 veces a la funcion pero la idea era que se notara la diferencia)

PD: Me gusta esa frase trilo-byte "De que sirve saber cosas interesantes si a nadien le importan"
 

Adjuntos

  • Prueba.zip
    1.3 MB · Visitas: 9
Última edición:
A ver, si no pudiera ver algo menor a 40kB en disco, no podría decirte con precisión que el resultado sea de 6344 bytes. Te puedo asegurar que si se puede ver diferencias en el tamaño del ejecutable.

Sin embargo probé el mismo código con el Keil 4 en un ARM LPC1768 (obviamente sin el scanf y el printf) y efectivamente el resultado fue el mismo en los dos casos (1384 bytes).

De todas formas supongo que puede depender del compilador, la configuración y como está encarado el código. Sin embargo, desde mi punto de vista, a medida que el código se hace más complejo, mientras menos recursos globales tengas, mucho mejor.
 
Atrás
Arriba