Ejemplos y Consejos Sobre C Para Estudiantes Del E.E.T. #3

Notificación: Estos ejemplos y consejos son proveídos a usted(es) con el objetivo de educarlo(s). El uso de esta información esta bajo la responsabilidad de usted(es). Yo no tomo responsabilidad en como usted(es) utilicen esta información.

Yo fui estudiante de la Escuela de Enseñanza Técnica #3 de San Isidro y recientemente se me dio la oportunidad de poder ayudar algunos estudiantes con sus proyectos relacionados con electrónica y programación. Este articulo fue escrito para la clase del día Martes 4 de Noviembre de 2014.

Estos consejos y ejemplos no tienen un orden especifico. Si encuentro mas consejos o ejemplos los agregare a este articulo.
Tambien a medida que vea el nivel de programacion de los estudiantes iré agregando mas información acorde a sus conocimientos.

Estos consejos y ejemplos están escritos en C.

 

  1. Todos los archivos de código tendrían que empezar con una introducción:
    /**
     * Titulo del Programa o Código
     * Nombre de Clase Cursando
     * Autores: Navoleti Chagorta, Pepone Huecone
     * Creado: Marzo 31, 2014
     * Modificado: Abril 02, 2014
     * Descripción: Este código hace a Jarvis hablar por el parlante.
     */

    Esta introducción es una forma legal de proteger el código como también permite a otro programador tener una idea general del código que vera a continuación.

  2. Elijan el estilo de escritura para las frases o palabras compuestas usadas en funciones y variables.
    Un estilo utiliza todas las letras en minúscula separadas por un “_”:

    int color_red = color_to_integer("#FF0000");

    Otro estilo se llama CamelCase donde las frases o palabras se conectan juntas y la primera letra de cada palabra es mayuscula:

    int colorRed = colorToInteger("#FF0000");

    Elijan un solo estilo y utilícenlo a lo largo del programa.

  3. Las contantes, valores que nunca cambian, van en frases y palabras todas en muscula separadas por “_”:
    #define COLOR_RED "#FF0000"
    const int COLOR_BLUE = 0xFF;
  4. Los nombres de las variables tienen que describir que valor se esta guardando. Ejemplo:
    const char* MENSAJE_ERROR_NO_MEMORIA = "[X] Error: Out of Memory\n";
    
    int cociente = 7;
    int divisor = 5;
    int resto = cociente % divisor;
  5. Intenten crear pequeñas funciones que sean cortas, que realicen una sola cosa, y que el nombre describa lo mejor posible la funcionalidad de la función.
    void intercambioDeValores(int *primerValor, int *segundoValor){
        int temporario;
        temporario = *primerValor;
        *primerValor = *segundoValor;
        *segundoValor = temporario;
    }

    Esta función realiza un intercambio de los valores guardados en dos variables y no retorna nada.
    las variables “a” y “b” son “punteros” y se explicaran mas adelante como trabajan.
    Al hacer las funciones cortas y enfocadas a un solo trabajo, uno puede re-usarlas, fácilmente repararlas, y reducir la cantidad de comentarios

  6. La función “debug” funciona como “fprint”, con la única diferencia que si el primer parámetro no es 1 entonces no imprime.
    Dependiendo del compilador, este codico capaz que no funcione
    Ustedes pueden mejorar la funcion “debug” para que les de otros mensaje por ejemplo.
    Eso se los dejo a ustedes para que lo modifiquen como mas les convenga.

    #include <stdio.h>
    #include <stdlib.h>
    #include <stdarg.h>
    #include <string.h>
    
    // 0 = hide, 1 = show
    #define TOGGLE_DEBUG 1
    
    int main(){
     debug(TOGGLE_DEBUG, "function: %s", "main()");
     debug(TOGGLE_DEBUG, "El numero es %d", 5);
     return 0;
    }
    
    void debug(int doShow, char *fmt, ...){
        if (doShow == 1){
            va_list argp;
            fprintf(stdout, "[DBG] ");
            va_start(argp, fmt);
            vfprintf(stdout, fmt, argp);
            va_end(argp);
            fprintf(stdout, "\n");
        }
    }

    Esto tendria que imprimir:
    [DBG] function main()
    [DBG] El numero es 5

  7. Para cargar la libreria de matematicas utilizen esta linea de codigo (si estan usando gcc como compilador):
    gcc yourfile.c -lm
  8. Comentarios tendrían que ser usados solamente cuando el código podría ser confuso para otra gente:
    #define M_PI 3.14159265358979323846264338327950288
    #define CIRCULO_EN_RADIOS M_PI / 180
    #define TIERRA_DIAMETRO_EN_KM 6367.0
    
    // Obtener distancia en kilometros entre coordenadas usando la formula de Harversine
    double obtenerDistanciaEnKM(double latitudOrigen
                                            , double longitudOrigen
                                            , double latitudDestino 
                                            , double longitudDestino){
    
      double latitudOrigenEnRadios = latitudOrigen * CIRCULO_EN_RADIOS;
      double longitudOrigenEnRadios = longitudOrigen * CIRCULO_EN_RADIOS;
      double latitudDestinoEnRadios = latitudDestino * CIRCULO_EN_RADIOS;
      double longitudDestinoEnRadios = longitudDestino * CIRCULO_EN_RADIOS;
    
      double differenciaLatitud = latitudDestinoEnRadios - latitudOrigenEnRadios;
      double differenciaLongitud = longitudDestinoEnRadios - longitudOrigenEnRadios;
    
      double differenciaEntrePuntos = pow(sin(differenciaLatitud / 2.0), 2)
                                                          + cos(latitudDestinoEnRadios) 
                                                          * cos(latitudOrigenEnRadios)
                                                          * pow(sin(differenciaLongitud / 2.0), 2);
    
      double distanciaAngular = atan2(sqrt(differenciaEntrePuntos), sqrt(1 - differenciaEntrePuntos));
    
      return distanciaAngular * TIERRA_DIAMETRO_EN_KM;
    }
  9. Aca hay un ejemplo de una funcion macro la cual se puede applicar con differentes tipos de variables como int, double, float
    Acuerdense que el pre-procesador solo hace reemplazos, asi que funciones macros con muchos codigos de linea es no productivo.

    #define max(a, b) \
        ({ typeof (a) _a = (a); \
            typeof (b) _b = (b); \
            _a > _b ? _a : _b; })

    “\” indica al pre-procesador que el código sigue en la próxima linea
    “typeof” hace que el pre-procesador remplace “typeof” por el tipo de la variable usada, por ejemplo: “typeof” pasa a ser “int” si “a” es del tipo int.
    “_a > _b ? _a : _b;” es otra forma de escribir un if statement. Si _a es major que “_b”, elibe a “_a” sino a “_b”

  10. C no tiene valores booleanos como TRUE, FALSE, YES o NO.
    Pero eso no quiere decir que no podemos simularlos.
    Para simularlos usaremos el “enum”

    typedef enum {
      FALSE,
      TRUE
    } bool;

    El valor FALSE sera 0 y el valor TRUE sera 1

  11. La mejor forma de mantener valores relacionados juntos es crear una nueva tipo de variable.
    Utilizaremos estructuras para eso:

    struct estructuraDeCoordenadas2D{
      int x;
      int y;
    } structCoordenadas2D;
    
    void main(){
      structCoordenadas2D puntoOrigen2D;
      puntoOrigen2D.x = 12;
      puntoOrigen2D.y = 25;
      mostrar(puntoOrigen2D);
    }
    
    void mostrar(structCoordenadas2D punto2D){
      printf("X: %d, Y: %d \n", punto2D.x, punto2D.y);
    }
  12. Esta tabla de caracteres constante pude ser de ayuda:
    \\ \
    \? ?
    \”
    \’
    \n Nueva linea
    \b Backspace
    \f Limpia la pantalla o saltea a la proxima pagina si se usa en impresoras
    \a Usar Alarma del parlante
    \r Carriage return
    \t Tab Horizontal
    \v Tab Vertical
    \ooo Numero octal “\o44”
    \xhh Numero hexagecimal “\x0F” o “\0x0F”

 

 

 

Share

Dangling and Wild Pointers in C/C++

When we define a variable, memory is allocated for that variable.

/* definition of a variable with a size memory allocated of one byte */
char character;

This declare that the name character is of type char.

As you may know, we use pointers in order to manipulate data in the memory. Pointers are designed to store a value which is a memory address. However, if a defined pointers is not instantiated then this pointer may have a memory address pointing to any place in memory. This is pointer is called a “dangling pointer”. For example,

/* Dangling pointer */
char* c_pointer;

char character;
char* c_char_pointer = &character;  /* No dangling pointer */

Lets assume we defined a dangling pointer and we attempt to print the content of the memory address that may store in the dangling pointer:

int* dangling_pointer;
printf('%d', *dangling_pointer);

This would give us segmentation fault.

Lets assume we define a pointer, integer_pointer,  and we make sure that this pointer is not dangling by instantiated with NULL. Later in the our program, we define a variable inside brackets, integer_variable, and we assign the address of this variable to our pointer. The moment we get out of the brackets, the variable integer_variable will be out of scope (This means that this variable disappear in our program).

int main(int argc, char* argv[]){

  int* integer_pointer = NULL;
  ... /* Dots means that there are more code not included in the example */

  { /* integer_variable exist only between this brackets */
    int integer_variable = 100;
    integer_pointer = &integer_variable;
    ...
  } /* After this bracket, integer_variable is out of scope */
  ...

  *integer_pointer = 150;
  ...

  return 0;
}

This could produce unpredictable behaviour because the address store in the integer_pointer could be pointing to a sector in memory used for another program or another part of the program.

When using dynamic memory allocation such as malloc or new, the moment that we free the pointer, it becomes dangling. Therefore, it is a good policy to point the pointer to NULL. Why? Because the memory that was obtained by malloc or new will not exist anymore since it was freed by free.

#include <malloc.h>
...
int main(int argc, char* argv[]){
  ...

  char* character_pointer = malloc(sizeof(char));
  ...

  /* character_pointer is freed and become a dangling pointer */
  free(character_pointer); 

  /* Point the pointer to NULL so it is not dangling anymore */
  character_pointer = NULL;
  ...

  return 0;
}

Sometimes is said that an dangling pointer is a wild pointer; however, there is a distinction. While a dangling pointer is a wild pointer, a wild pointer may not be a dangling pointer… What?!! (You may ask) Let me explain.

The different is the instantiation of the variable. Lets define to pointers:

char* character_pointer_1;
char* character_pointer_2;

Both pointers can be called dangling and/or wild pointers because they are not instantiated. Now lets assume we instantiate character_pointer_1.

char* character_pointer_1;
char* character_pointer_2;
...
character_pointer_1 = malloc(sizeof(char));
...
free(character_pointer_1);
...

In this case, things change because character_pointer_1 was instantiated and later freed (with free). character_pointer_1 is called dangling pointer for the fact that it was instantiated before, while character_pointer_2 is called wild because it was never instantiated.

Share

Function Pointers in C/C++

In the previous post, we cover how to work pointers with arrays. In this post we will see how to use function pointers, and how useful they can be:

I am assuming that you read the previous postings about pointers and pointers with arrays.

As we may recall, when we declare a pointer, the pointer will store an address of a position in memory.
Normally, we wish to create pointers of the same kind as the object we want to point at. For example, if the variable is an integer then we want the pointer to be an integer:

int i_variable = 22;
int* pi_variable = &i_variable;

If you create a different variable of the same kind, you could change where the pointer is pointing at:

int i_variable = 22;
int i_variable_2 = 44;
int* pi_variable = &i_variable;
printf('Value pointed at: %d \n', *pi_variable);     /* This line print 22 */
pi_variable = &i_variable_2;                         /* pi_variable points at i_variable_2 */
printf('New Value pointed at: %d \n', *pi_variable); /* This line prints 44 */

Now the question comes, can we use this with functions? Yes, we can!

Here is an example of how this works:

/* This return the addition of value_a with value_b */
int add(int value_a, int value_b){
	return value_a + value_b;
}

/* This function return the subtraction of value_b from value_a */
int sub(int value_a, int value_b){
  return value_a - value_b;
}

int main(int argc, char* argv[]){
  int val_a = 4;   
  int val_b = 5;

  /* Function pointer  must have the same return type and parameter type */
  int (*p_function)(int, int);

  p_function = add;
  printf('ADD A: %d with B:%d to obtain %d \n', 
         val_a, 
         val_b, 
         (*p_function)(val_a, val_b));

  p_function = sub; 
  printf('SUBTRACT B: %d OF A:%d to obtain %d \n', 
         val_b, 
         val_a, 
         (*p_function)(val_a, val_b));

return 0;
}

This will print:

ADD A: 4 with B:5 to obtain 9
SUBTRACT B: 5 OF A:4 to obtain -1

As you can see this can be a very powerful feature. The function pointer will point to the address of any function we want to point at while the function have the same return type (int in this case), the same number of parameters (in this case, we have two parameters), and the same type of parameters (both parameters are int).

Share

Pointers with Arrays in C/C++

In the previous post, “Pointers in C/C++”, we talked about pointers in C/C++. We recommend to read this posting before continuing.

Lets say we declare an array of 5 elements:

int i_array[5];

Also, we could declare and instantiate each elements in the array at the same time:

int i_array[5] = {10, 20, 30, 40, 50};

Now, lets assume we created the array and we want to change the values of each element, one way could be:

i_array[0] = 15;
i_array[1] = 25;
i_array[2] = 35;
i_array[3] = 45;
i_array[4] = 55;

However, there is another way to change the values of elements in an array, and that way is by using pointers.

To begin with, first we need to understand how are the array build, each element in the array have an address in memory, the name that we give to the array is connected to the address of the first element. Each consecutive elements will have an address that is the previous address plus the size of the address. For example, if you have an array of characters, each character have a size of 8 bits (1 byte), this means that each element’s address will be different by 1 byte.

For example, this  code would print the address of each element in an array:

int main(int argc, char* argv[]){
  int c_array[5] = {'a', 'b', 'c', 'd', 'e'};
  int index;
  for (index = 0; index < 5; ++index){
    printf('c_array[%d] with value %d has address 0x%X \n',
     	     index, c_array[index], (unsigned int) &c_array[index]);
  }
  return 0;
}

This will show us:

c_array[0] with value a has address 0xBF95EAB7
c_array[1] with value b has address 0xBF95EAB8
c_array[2] with value c has address 0xBF95EAB9
c_array[3] with value d has address 0xBF95EABA
c_array[4] with value e has address 0xBF95EABB

As you can see they are separated by one byte:

0xBF95EAB7 - 0xBF95EAB8 = 1

The graphic representation of this array is:

When working with pointers, we can access to the information indirectly by just using the address of each element in the array. The following code will make a pointer to point at the first element of the array:

char* p_c_array = c_array; /* Not need &. c_array return an address. c_array[0] return a value */

The reason we are not using & when assigning the address is that arrays are accessed by reference, which means that the compiler will return the address of the first element if we write c_array while if we write c_array[0] it will return the value in that position.

If we want to print the first element:

printf('First Element: %c', *p_c_array);

If we want to print the second element, we need to increase the pointer so it will point to the next element.
By increasing it means that we must increase a total amount of one byte (because we are talking about char variables) to access to the next element:

0xBF95EAB7 + 1 byte =  0xBF95EAB8 

There are two ways to increase value in the pointer by one byte:
One ways is:

p_c_array = p_c_array + sizeof(char); /* Size of char return 1 byte */

Or by letting the compiler to increase the value:

p_c_array++;

Just take in consideration that you don’t wish to loose the original address to which you are pointing to the first element of the array; therefore it common practice to create a second pointer that will have the same address and increase that pointer, for example:

char* p_c_array = c_array;  /* Point to first element of array */
char* p_c_array_2;
p_c_array2 = p_c_array;      /* Copy address stored in p_c_array */
p_c_array2++;                /* Increase address stored by one byte to point to next element */
printf('First element: %c. Second Element %c \n',
       *p_c_array,
       *p_c_array2);

The follow example would print all the elements of the array:

int main(int argc, char* argv[]){
  char c_array[5] = {'a', 'b', 'c', 'd', 'e'};
  char* p_c_array = c_array;
  char* p_c_array_2;
  for (p_c_array_2 = p_c_array;
       *p_c_array_2 != '\0';
       ++p_c_array_2){
    printf('p_c_array_2 points to variable with value %c \n',
           *p_c_array_2);
  }
  return 0;
}

Notice that we are using in this case ‘\0’ to indicate the end of the array. This do not apply if we would be using an array of integers for example.

This code will print to screen:

p_c_array_2 points to variable with value a
p_c_array_2 points to variable with value b
p_c_array_2 points to variable with value c
p_c_array_2 points to variable with value d
p_c_array_2 points to variable with value e

We this we had cover the basic about pointers and arrays.

Next post, we are going to talk about pointer functions

Share

Pointers in C/C++

When programming in C and/or C++, you will encounter the use of pointers.
We are going to begin first doing a review of regular pointers and then we will see how to create and use function pointers.

In C/C++, when we create a static or dynamic variable, this variable will have an address in memory. For example:

If we write the following code:

int integer_variable;

An space in memory of the integer size (16-bits, 32-bits, or 64-bits depending the system) will be reserved.

When we assign a value to this variable, this value is store in the memory as a binary number that is represented base on the variable’s type.

int integer_variable = 4;
integer_variable = 5; /* Change value */

If we wish to know in which address is this value store we use ‘&’ in front of the variable.

int main(int argc, char* argv[]){
  int i_variable = 22;
  printf('i_variable has value %d at address 0x%X\n', i_variable, (unsigned int) &i_variable);
  return 0;
}

First note that we are using a downcasting “(unsigned int)” since there are no negative values in memory address else you will receive a warning from the gcc compiler. Second, in this example we are using %X to indicate printf to represent the address as an hexadecimal value.

This will give us:

i_variable has value 22 at address 0xBF9EFC28

If this would be a house for example, i_variable would be the name of the resident while the memory address would be the address of the house.

When creating a pointer in C/C++, we are declaring a variable (which have its own address) that will hold an address.

int* p_i_variable;

And we instantiate this pointer variable with the address of another variable, in this example would be i_variable.

int* p_i_variable = &i_variable;

or you could instantiate in this way:

int* p_i_variable;
p_i_variable = &i_variable;

So, we could say that the pointer p_i_variable is like a P. O. Box which many home-owners use when having companies. The P. O. Box have an address but the content of it is pointed to the real address of the owner.

If we want to ask where the p_i_variable (P.O. Box) is “pointing at” we can do:

int main(int argc, char* argv[]){
  int i_variable = 22;
  int* p_i_variable = &i_variable;
  printf('i_variable with value %d has address 0x%X \n', i_variable, (unsigned int) &i_variable);
  printf('p_i_variable (P.O Box with address 0x%X) is pointing to address 0x%X\n',
         (unsigned int) &p_i_variable,
         (unsigned int) p_i_variable);
}

You may notice that the first parameter is (unsigned int) &p_i_variable, this is to display the address of the pointer variable, while (unsigned int) p_i_variable is to display the value store in that pointer variable which would be the address of i_variable. Here is an example of the output:

i_variable with value 22 has address 0xBF877448
p_i_variable (P.O Box with address 0xBF877444) is pointing to address 0xBF877448

Lets say that want to change the value stored in i_variable. You could changed the value directly by using the variable name:

i_variable = 50;

Or you could do it indirectly by using the pointer:

*p_i_variable = 50;

First notice that we are using the asterisk ‘*’ before the name of the pointer. This tells the compiler the following: To the variable that have the address stored in p_i_variable, we wish to change the value to 50;

For example:

int main(int argc, char* argv[]){
  int i_variable = 22;
  int* p_i_variable = &i_variable;
  printf('i_variable with value %d has address 0x%X \n', i_variable, (unsigned int) &i_variable);
  printf('p_i_variable (P.O Box with address 0x%X) is pointing to address 0x%X\n',
         (unsigned int) &p_i_variable,
         (unsigned int) p_i_variable);

  /* Change value of i_variable directly */
  i_variable = 50;
  printf('i_variable with value %d has address 0x%X \n', i_variable, (unsigned int) &i_variable);

  /* Change value of i_variable indirectly using the pointer p_i_variable */
  *p_i_variable = 100;
  printf('p_i_variable (P.O Box with address 0x%X) is pointing to address 0x%X\n',
         (unsigned int) &p_i_variable,
         (unsigned int) p_i_variable);
  printf('i_variable with value %d has address 0x%X \n', i_variable, (unsigned int) &i_variable);
  printf('Obtain value of i_variable using pointer p_i_variable: %d\n', *p_i_variable);

  return 0;
}

This would show:

i_variable with value 22 has address 0xBF982BC8
p_i_variable (P.O Box with address 0xBF982BC4) is pointing to address 0xBF982BC8
i_variable with value 50 has address 0xBF982BC8
p_i_variable (P.O Box with address 0xBF982BC4) is pointing to address 0xBF982BC8
i_variable with value 100 has address 0xBF982BC8
Obtain value of i_variable using pointer p_i_variable: 100

One of the things you must have in consideration is the case of dangling pointers. A dangling pointer is a pointer that points to an invalid direction in the memory.

If we create a pointer variable for example:

/* This is a dangling pointer */
int *pointer_integer;

int integer;

/* Now pointer_integer is not a dangling pointer anymore */
pointer_integer = &integer;

and right away we try to read from it, the value inside the variable can be any value, it could be pointing to any direction in memory. If we try to write anything to that direction we could create a segmentation fault.

So if we are not going to use this variable right away, it is a good idea to instantiated with NULL (which have a value of 0):

/* this is not a dangling pointer */
int *pointer_integer = NULL;

The next post, we are going to talk about pointers with arrays, and double-pointers.

Share