Skip to content

Instantly share code, notes, and snippets.

@Edmartt
Last active August 23, 2020 01:56
Show Gist options
  • Save Edmartt/c179b8e81ed18a8d455e1705e4baa175 to your computer and use it in GitHub Desktop.
Save Edmartt/c179b8e81ed18a8d455e1705e4baa175 to your computer and use it in GitHub Desktop.
Cifrado de César sin caracteres extraños.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 100;
void cifrar(char*,int);
void mostrarMenu();
int main(int argc, char **argv)
{
int offset=0;
char word[500];
char* ptrword=NULL;
printf("Ingrese la palabra a cifrar: ");
scanf("%s500",word);
ptrword=malloc(strlen(word));
if(ptrword!=NULL) {
strcpy(ptrword,word);
printf("\nIngrese el desplazamiento de caracteres: ");
scanf("%d",&offset);
cifrar(ptrword,offset);
free(ptrword);
} else {
printf("\nNo hay suficiente memoria\n");
free(ptrword);
return -1;
}
printf("\n");
return 0;
}
void cifrar(char* cadena,int offset)
{
int dif,car;
int len=strlen(cadena);
int asciiMay=65,asciiMin=97;
for(int i=0; i<len; i++) {
if(cadena[i]+offset>90 && cadena[i]<=96) {
dif=90-cadena[i];
car=offset-dif;
cadena[i]=asciiMay+car-1;
} else if(cadena[i]+offset <=90) {
cadena[i]=cadena[i]+offset;
} else if(cadena[i]+offset>122) {
dif=122-cadena[i];
car=offset-dif;
cadena[i]=asciiMin+car-1;
} else if(cadena[i]<=122) {
cadena[i]=cadena[i]+offset;
}
printf("%c ",cadena[i]);
}
@A-deLuna
Copy link

Literales

En vez de usar valores ASCII numéricos puedes ingresar el caracter directamente, por ejemplo:

int asciiMay='A', asciiMin='a';

En C los caracteres literales representan bytes y caben en un entero.

Wrap around con módulo

Podrías simplificar la lógica para darle vuelta a los caracteres usando módulo. Algo así como:

#define TOTAL_LETRAS = 26;
cadena[i] = 'a' + (cadena[i]-'a'+offset) % TOTAL_LETRAS; 

cadena[i]-'a' te da un número del 0 al 25 de cada letra, le sumas el offset, si se pasa de 26 nos quedamos con el restante, y finalmente sumas este número de 0 a 25 con 'a' para encontrar el valor ascii del caracter.

Ahora sólo necesitas manejar un caso para mayúsculas y otro para minúsculas.

Lectura

scanf("%s500",word); parece incorrecto, tal vez querías hacer scanf("%499s",word);, el límite de caracteres va antes del especificador del formato. Y recuerda que al final del string necesitas tener un caracter nullo para indicar el fin de la palabra.

No necesitas ptrword podrías pasar word directamente a tu función de cifrar. Cuando pasas un arreglo a una función decimos que el arreglo se 'degrada' a un apuntador. Dentro de la función vas a recibir un apuntador al primer elemento del arreglo.

@Edmartt
Copy link
Author

Edmartt commented Aug 21, 2020

Literales

En vez de usar valores ASCII numéricos puedes ingresar el caracter directamente, por ejemplo:

int asciiMay='A', asciiMin='a';

En C los caracteres literales representan bytes y caben en un entero.

Wrap around con módulo

Podrías simplificar la lógica para darle vuelta a los caracteres usando módulo. Algo así como:

#define TOTAL_LETRAS = 26;
cadena[i] = 'a' + (cadena[i]-'a'+offset) % TOTAL_LETRAS; 

cadena[i]-'a' te da un número del 0 al 25 de cada letra, le sumas el offset, si se pasa de 26 nos quedamos con el restante, y finalmente sumas este número de 0 a 25 con 'a' para encontrar el valor ascii del caracter.

Ahora sólo necesitas manejar un caso para mayúsculas y otro para minúsculas.

Lectura

scanf("%s500",word); parece incorrecto, tal vez querías hacer scanf("%499s",word);, el límite de caracteres va antes del especificador del formato. Y recuerda que al final del string necesitas tener un caracter nullo para indicar el fin de la palabra.

No necesitas ptrword podrías pasar word directamente a tu función de cifrar. Cuando pasas un arreglo a una función decimos que el arreglo se 'degrada' a un apuntador. Dentro de la función vas a recibir un apuntador al primer elemento del arreglo.

Gracias por esta respuesta. Siempre se puede hacer mejor todo.

Sí, intentaba hacer eso en la lectura, se me pasó hacerlo así y me dejé llevar que no me dio error para nada. 👍
Aunque no entiendo lo del caracter nulo, es decir, sé lo que es, pero, ¿por qué pondría 499 en lugar de 500 si al final el caracter nulo?

@A-deLuna
Copy link

Aunque no entiendo lo del caracter nulo, es decir, sé lo que es, pero, ¿por qué pondría 499 en lugar de 500 si al final el caracter nulo?

Muy buena pregunta.

Te hice este programa para que veas el problema:

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char s[4];
  char b = 2;
  scanf("%4s",s);
  printf("%s %d \n", s, b);
}

compilalo sin optimizaciones usando gcc -O0 a.c y correlo usando echo "1234" | ./a.out

Tal vez esperarías que imprimiera 1234 2, pero en mi computadora imprime 1234 0

¿Puedes ver el problema?

A scanf le decimos que lea a lo mucho 4 caracteres. Lee "1234" y los escribe en s. Pero todos los strings necesitan terminar en un caracter nulo, que es lo mismo al valor 0.

Pero scanf ya no tiene espacio para escribir el caracter nulo en s. Entonces acaba sobre-escribiendo la sienguiente posición en memoria!

b ahora vale 0 cuando lo imprimimos.

@MrDave1999
Copy link

MrDave1999 commented Aug 22, 2020

Hola @wormholesepiol

Mis observaciones

1.- Sí se va a pedir una cadena por medio de scanf, se debería de especificar el tamaño del array de esta forma:

scanf("%499s",word);

Se escribe 499 porque se debe dejar un espacio para el caracter nulo, de lo contrario, ocurrirá otro desbordamiento de búfer (para más información mirar este enlace: ¿Cómo evitar un desbordamiento de búfer cuando se pide una cadena con scanf?). También se debe tomar en cuenta que cuando desbordas un array, el comportamiento es indefinido, así que el resultado que esperes en tiempo de ejecución es arbitrario, puede suceder cualquier cosa. Por ejemplo, un posible resultado sería sobrescribir la propia memoria del programa (esto es difícil de depurar) o también podrías intentar acceder a una dirección de memoria que ya está en uso, en esos casos, es usual que el sistema operativo mate el proceso actual.

2.- No dejas espacio para el caracter nulo cuando pides memoria con malloc:

ptrword=malloc(strlen(word));

Luego invocas a la función strcpy:

strcpy(ptrword,word);

Ahí está el problema, otra vez sucederá un desbordamiento de búfer. El puntero ptrword apunta a un array de N caracteres, así que si el usuario ingresa 20 caracteres, el array que reservas tendrá disponible 20 posiciones por caracter. Entonces la función strcpy copiará los 20 caracteres en el array que apunte ptrword y el valor del caracter nulo lo escribe en una dirección adyacente de la última posición.

Por ejemplo, si el usuario ingresa 5 caracteres, el array word (asumamos que su tamaño es de 6) es reflejado así en memoria:

0xA    0xB    0xC      0xD   0xE     0xF
h       o      l        a     s      \0

Luego, se ejecuta la función strpcy y copia los caracteres del array word en el array que apunte ptrword. Aquí se debe tomar en cuenta que el tamaño de este array será de 5 elementos:

              0x20        0x21     0x22     0x23    0x24    0x25
                h          o        l        a         s      \0

Como no dejamos un espacio para el caracter nulo, strcpy escribe el caracter nulo en la dirección próxima (o dirección adyacente) de 0x24, que en este caso sería 0x25. Aquí sucede otro desbordamiento de búfer, que a su vez, es un comportamiento indefinido.

Esa es la razón del porque se debe dejar un espacio para el caracter nulo:

ptrword=malloc(strlen(word) + 1);

3.- No es bueno asumir que un char equivale SIEMPRE a un 1 byte, esto depende de la arquitectura de la máquina. Por ende, lo normal sería reservar con malloc de esta forma:

ptrword=malloc((strlen(word) + 1) * sizeof(char));

Para más información, mirar este enlace: ¿Qué es CHAR_BIT?.

4.- En sí no necesitas usar memoria dinámica en este caso. Con el array word bastaría. Eso ahorra liberar la memoria y también evitas ejecutar la función strcpy.

En fin, el programa me parece que está bien, aunque según la wikipedia hay una forma rápida de hacer el cifrado de césar y es con la siguiente fórmula:

x + n % 27

Donde x es el número de la letra y n es el desplazamiento. Aquí simplemente se debe convertir la letra a un dígito.

Por ejemplo, si la letra es B, su equivalente a código ASCII sería 66, para convertir a un número entero sería:

66 - 65 = 1

Así es, se debe restarle con la letra inicial del alfabeto y justamente ese 1 es el número asociado a la letra, donde B = 1, con eso simplemente se le suma el offset (asumamos que es 3) y da como resultado:

1 + 3 % 27 =
4 % 27 =
4

Sí seguimos esta tabla:

A = 0,
B = 1,
C = 2,
D = 3,
E = 4

Nos queda claro que el 4 es el dígito asociado a la letra E, solo faltaría sumarle un 65 (porque es el código ASCII inicial del alfabeto) para poder convertirlo a caracter:

65 + 4 =
69  ---> E

Lo bueno de usar el operador módulo es que si el resultado de x + n da un valor mayor o igual a 27, regresa al inicio (como la manilla del reloj).

Ejemplo:

Sí la letra a codificar es Z, entonces su número asociado sería 26. Al sumarle el offset nos daría 29 y el resto o módulo sería:

29 % 27 =
2

Nos dio 2 y justamente corresponde al caracter C y listo, de este modo nunca nos sobrepasamos de la cantidad máxima del alfabeto 👍

En código quedaría así:

#include <stdio.h>
//Desplazamiento.
#define OFFSET 3
//Obtiene la letra inicial del alfabeto, ya sea mayúscula o minúscula.
#define getLetter(letter) ((letter >= 'a' && letter <= 'z') ? ('a') : ('A'))

void decode(char*);
void encode(char*);

int main(void)
{

	char str[40];
	printf("Ingrese una cadena: ");
	scanf("%39s", str);
	
	decode(str);
	printf("Codificado: %s\n", str);
        return 0;
}
void encode(char* buf)
{
	for(int i = 0; buf[i] != '\0'; ++i)
		buf[i] = getLetter(buf[i]) + (((buf[i] - getLetter(buf[i])) + OFFSET) % 26);
}

@Edmartt
Copy link
Author

Edmartt commented Aug 22, 2020

Aunque no entiendo lo del caracter nulo, es decir, sé lo que es, pero, ¿por qué pondría 499 en lugar de 500 si al final el caracter nulo?

Muy buena pregunta.

Te hice este programa para que veas el problema:

#include <stdio.h>
#include <string.h>

int main(int argc, char **argv)
{
  char s[4];
  char b = 2;
  scanf("%4s",s);
  printf("%s %d \n", s, b);
}

compilalo sin optimizaciones usando gcc -O0 a.c y correlo usando echo "1234" | ./a.out

Tal vez esperarías que imprimiera 1234 2, pero en mi computadora imprime 1234 0

¿Puedes ver el problema?

A scanf le decimos que lea a lo mucho 4 caracteres. Lee "1234" y los escribe en s. Pero todos los strings necesitan terminar en un caracter nulo, que es lo mismo al valor 0.

Pero scanf ya no tiene espacio para escribir el caracter nulo en s. Entonces acaba sobre-escribiendo la sienguiente posición en memoria!

b ahora vale 0 cuando lo imprimimos.

Creo que he entendido, aunque no estaba seguro.

Es decir, mi array tendría declarado 500 bytes, pero al leer la entrada es donde debo decirle que solo lo haga con 499. Esa es la parte que me confundía, estaba relacionando declararlo de ese tamaño con leerlo de ese tamaño. Bien aclaratorio, en caso de no estar yo entendiendo mal, claro está.

@MrDave1999
Copy link

Así es. En el primer parámetro debes agregarle si o si, el 499, para dejar espacio para el caracter nulo :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment