CS50 Caesar Cipher Bug - c

Okay, so I am completely stumped. I cannot understand why this programs output acts as if there is a random key everytime.
This program:
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
string sKey = argv[1];
// Make sure program was run with just one command-line argument
if (argc != 2 || atoi(argv[1]) < 0)
{
printf("Usage: ./caesar key\n");
return 1;
}
//Counts length of string and checks if all chars are digits
int counter = 0;
for (int i = 0; i < strlen(sKey); i++)
{
if isdigit(sKey[i])
{
counter++;
}
}
//Checks if the key is a number
if (counter != strlen(sKey))
{
printf("Usage: ./caesar key\n");
return 1;
}
// Convert argv[1] from a `string` to an `int`
int key = (int)sKey;
// Prompt user for plaintext
string plaintext = get_string("Plaintext: ");
printf("Ciphertext: ");
for (int i = 0; i < strlen(plaintext); i++)
{
if (isalpha(plaintext[i]) && isupper(plaintext[i]))
{
printf("%c", (((plaintext[i] - 'A') + key) % 26) + 'A');
}
else if (isalpha(plaintext[i]) && islower(plaintext[i]))
{
printf("%c", (((plaintext[i] - 'a') + key) % 26) + 'a');
}
else
{
printf("%c", plaintext[i]);
}
}
printf("\n");
}
Will output this:
caesar/ $ ./caesar 1
Plaintext: Hello, I'm Justin.
Ciphertext: Fcjjm, G'k Hsqrgl.
caesar/ $ ./caesar 1
Plaintext: Hello, I'm Justin.
Ciphertext: Pmttw, Q'u Rcabqv.
It seems to be due to the modulo operator, because when I isolated it I could recreate the issue. Is it one of my included libraries? I solved the problem on my own and ended up looking up a solution on youtube only to find my solution performed the same operations as the correct solution. I must be missing something.
Thank you

This is because int key = (int)sKey; does NOT convert the string to an integer... at least not in the way you think it does. It takes the string pointer sKey (a memory address) to an integer. Since every time you run the progra this can be a different address, this is why it looks random. The correct way to convert a numerical string to a value is using atoi or strtol. The first part of your program should be something like this:
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
string sKey = argv[1];
int i;
// Make sure program was run with just one command-line argument
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
// Checks if all chars are digits
for (int i = 0; sKey[i]; i++)
if (!isdigit(sKey[i]) break;
// If the key contains any non-digits, error
if (sKey[i])
{
printf("Usage: ./caesar key\n");
return 1;
}
// Convert argv[1] from a `string` to an `int`
int key = atoi(sKey);
// The rest should be fine

Related

it compiles ok but when I try to run it with a digit it says segmentation fault what do I do?

#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, string argv[])
{ // see if it is correct input if not then reset and print the right way
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
for (int i = 0; i < strlen(argv[1]); i++)
{
if (!isdigit(argv[1]))
{
printf("Usage: ./caesar key\n");
return 1;
}
// cipher the text
int k = atoi(argv[1]);
string plaintext = get_string("Plain text: ");
printf("Cipher text: ");
// printing out the ciphered text
for (int j = 0; j < strlen(plaintext); j++)
if (isupper(plaintext[j]))
{
printf("%c", (plaintext[j] - 65 + k) % 26 + 65);
}
else if (islower(plaintext[j]))
{
printf("%c", (plaintext[j] - 97 + k) % 26 + 97);
}
else
{
printf("%c", plaintext[j]);
}
}
printf("\n");
}
the problem is that when I run it as it should be it only says segmentation fault. I think that the problem is at the top but im not sure
Most of your problem is simply trying to deal with the logic needed to handle that argument Cipher key. Remember, it is an input like any other, so it should be treated first, separately.
Also, avoid aliases for standard objects. It does absolutely nothing to improve readability. (It does the opposite, in fact. Any C programmer will first ask: “what is different about a string and a char *?” And then spend time only to figure out that there isn’t any. So, don’t do that.)
Finally, avoid magic numbers when dealing with character strings. Just use the character literal directly. It makes life so much easier when reading the code.
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char * argv[]) // DON’T use aliases like “string” for “char *”
{
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
int k = atoi(argv[1]); // try to turn the argument string into the Caesar offset value
if (k == 0) // fail if non-numeric input (or if input is actually “0”)
{
printf("Invalid key\n");
return 1;
}
const char * plaintext = get_string("Plain text: ");
// Convert the plaintext to the ciphertext
printf("Cipher text: ");
for (int j = 0; j < strlen(plaintext); j++)
{ // <-- don’t forget compound statement braces
if (isupper(plaintext[j]))
{
printf("%c", (plaintext[j] - 'A' + k) % 26 + 'A'); // prefer 'A' instead of 65
}
else if (islower(plaintext[j]))
{
printf("%c", (plaintext[j] - 'a' + k) % 26 + 'a');
}
else
{
printf("%c", plaintext[j]);
}
}
printf("\n");
return 0;
}
Apart from those minor problems, you did a pretty good job! Keep it up!
EDIT
Oh yeah, almost forgot. Did you need to free(plaintext) before main() terminates?

Not sure how to proceed with Caesar's algorithm, cannot print out the deciphered text

I'm currently trying to convert a plaintext to a ciphertext with a key.
Code:
#include <stdio.h>
#include <cs50.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
int atoi(string argv);
int main(int argc, string argv[])
{
int k = 0;
string key = argv[1];
if (argc == 2)
{
for (k = 0; k < strlen(key); k++)
{
if (!isdigit(key[k]))
{
printf("\nUsage: %s key\n", argv[0]);
return 1;
}
else
{
printf("\nSucces!");
}
}
}
else
{
printf("\nUsage: %s key\n", argv[0]);
return 1;
}
string plaintext = get_string("\nplaintext: ");
for (int i = 0; i < strlen(plaintext); i++)
{
char c = plaintext[i];
if (isalpha(c))
{
printf("%c", (c + key) % 26);
}
}
}
If the character in the string of plaintext is a letter from the alphabet, I want it to move key positions.
I need to apply the following formula: ci = (pi + k) % 26, but it doesn't work out for me. Can someone hint me what I'm doing wrong?
I get the following error:
error: invalid operands to binary expression ('string' (aka 'char *') and 'int')
This (c + key) is the binary expression that provokes the error. c is a char (which is technically an int). key is a string, as declared and initialized here string key = argv[1];. There is a function in stdlib.c library called atoi, so it is a questionable decision to declare your own. That library needs to be include'd to use the atoi function. That is the function that can be used to convert key to an integer.

CS50 Caesar program is working but check50 says it isn't

I created this program but I'm getting errors on CS50 showing that I didn't do any of it correctly.
The requirements are as follows:
Implement your program in a file called caesar.c in a directory called caesar.
Your program must accept a single command-line argument, a non-negative integer. Let’s call it k for the sake of discussion.
If your program is executed without any command-line arguments or with more than one command-line argument, your program should print an error message of your choice (with printf) and return from main a value of 1 (which tends to signify an error) immediately.
If any of the characters of the command-line argument is not a decimal digit, your program should print the message Usage: ./caesar key and return from main a value of 1.
Do not assume that k will be less than or equal to 26. Your program should work for all non-negative integral values of k less than 2^31 - 26. In other words, you don’t need to worry if your program eventually breaks if the user chooses a value for k that’s too big or almost too big to fit in an int. (Recall that an int can overflow.) But, even if k is greater than 26, alphabetical characters in your program’s input should remain alphabetical characters in your program’s output. For instance, if k is 27,
A should not become [ even though [ is 27 positions away from A in ASCII, per http://www.asciichart.com/[asciichart.com]; A should become B, since B is 27 positions away from A, provided you wrap around from Z to A.
Your program must output plaintext: (without a newline) and then prompt the user for a string of plaintext (using get_string).
Your program must output ciphertext: (without a newline) followed by the plaintext’s corresponding ciphertext, with each alphabetical character in the plaintext “rotated” by k positions; non-alphabetical characters should be outputted unchanged.
Your program must preserve case: capitalized letters, though rotated, must remain capitalized letters; lowercase letters, though rotated, must remain lowercase letters.
After outputting ciphertext, you should print a newline. Your program should then exit by returning 0 from main.
My code:
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
int main(int argc, string argv[])
{
//check if k inputed
if (argc != 2)
{
printf("Usage: ./caesar key\n");
return 1;
}
//value k is the number after ./caesar
int k = atoi(argv[1]) % 26;
int x = 0;
int s = strlen(argv[1]);
//check if k is a positive integer
if (k < 0)
{
printf("Usage: .caesar key\n");
return 1;
}
else
{
//check for arguments
for (int i = 0; i < s; i++)
{
if (isalpha (argv[1][i]))
{
continue;
}
else if (isalnum (argv[1][i]))
{
x++;
}
else
{
continue;
}
}
if (x != s)
{
printf("Usage: ./caesar key\n");
}
else if (x == s)
{
//get plaintext
string plain_text = get_string("plaintext: ");
printf("ciphertext: ");
for (int y = 0; y <= strlen(plain_text); y++)
{
//change letters
if (isalpha(plain_text[y]))
{
char p = plain_text[y];
int cipher_int = p + k;
if (isupper(p))
{
while(cipher_int >= 90)
{
cipher_int -= 26;
}
char cipher_text = cipher_int;
printf("%c", cipher_text);
}
if (islower(p))
{
while(cipher_int >= 122)
{
cipher_int -= 26;
}
char cipher_text = cipher_int;
printf("%c", cipher_text);
}
}
else
{
printf("%c", plain_text[y]);
}
}
printf("\n");
}
}
return 0;
}
It appears that your wrapping is not working correctly. I found that when I used 3 as the key and put "The quick fox jumps over the lazy brown dog." as the plain text, "brown" became "eur`q" when it should be "eurzq". I think you're using >= in your wrapping comparisons when you should use >.
Your check for digits is very cumbersome and does not cause the program to return 1 as required if the argument is incorrect.
Here is a simpler test:
//check for arguments
for (int i = 0; i < s; i++) {
if (!isdigit((unsigned char)argv[1][i])) {
printf("Usage: ./caesar key\n");
return 1;
}
}
Also note that you should stop the encoding loop when the index == the length of the string. therefore the operator should be <.
Another problem is the use of isalpha() and similar functions from <ctype.h> with char values. These functions are undefined for negative values (except EOF). Some platforms define char as signed by default, making isalpha(plaintext[y]) have undefined behavior if the user typed non ASCII text. Cast the argument as (unsigned char) to avoid this problem.
Furthermore, you should not use hardcoded ASCII values such as 90 and 122, use character constants such as 'a' and 'z' for better readability. Doing so would make another error in your encoding loop more obvious: while(cipher_int >= 90) should be if (cipher_int > 'A') and while(cipher_int >= 122) should be if(cipher_int > 'z').
Here is a modified version:
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, string argv[])
{
// check for a single command line argument
if (argc != 2) {
printf("Usage: ./caesar key\n");
return 1;
}
char *arg = argv[1];
if (*arg == '\0') {
printf("caesar: key cannot be an empty string\n");
return 1;
}
// check that the argument is a non negative number
for (size_t i = 0; arg[i]; i++) {
if (!isdigit((unsigned char)arg[i])) {
printf("Usage: ./caesar key\n");
return 1;
}
}
// value k is the shift number after ./caesar
int k = atoi(argv[1]) % 26;
// get plaintext
string plain_text = get_string("plaintext: ");
printf("ciphertext: ");
for (size_t i = 0; plain_text[i] != '\0'; i++) {
unsigned char c = plain_text[i];
// change letters
if (islower(c)) {
putchar('a' + ((c - 'a') + k) % 26);
} else
if (isupper(c)) {
putchar('A' + ((c - 'A') + k) % 26);
} else {
putchar(c);
}
}
printf("\n");
return 0;
}

C program doesnt handle non numeric key

Good evening people hope yall are doing well, i come to you all because i need some help regarding some C code i wrote, keep in mind that i just started coding in C so sorry if this question sounds dumb.
Basically I am doing the CS50 and we are writing a program that encripts messages so we first require the user for a command line argument which will be the key we will use to transform the plain text into cipher text. so basically if the user runs the command with lets say a 2 i.e: ./caesar 2 all the words he will type later will be "run" two sides.
my program works how is supposed and if the user types a letter instead of a number the program wont prompt for the users message. however, if the user runs the command line like this for example, ./caesar 8x the program will run eventhough the user typed a letter so any ideas on how to iterate thru the user command argument and if there is a letter the program should run? thanks!
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <cs50.h>
int main(int argc, string argv[])
{
if (argc == 2 && isdigit(*argv[1]))
{
int key = atoi(argv[1]);
string plaintext = get_string("plaintext: ");
printf("ciphertext: ");
for (int i = 0, n = strlen(plaintext); i < n; i++)
{
if (isupper(plaintext[i]))
{
printf("%c", (((plaintext[i] + key) - 65) % 26) + 65);
}
else if (islower(plaintext[i]))
{
printf("%c", (((plaintext[i] + key) - 97) % 26) + 97);
}
else
{
printf("%c", plaintext[i]);
}
}
printf("\n");
return 0;
}
else if (argc == 1)
{
printf("NO KEY\n");
return 1;
}
else if (argc >= 3 || argv[1] == (string) argv[1])
{
printf ("Usage: ./caesar key\n");
return 1;
}
}
EDIT. SOLVED
this is how the code ended up looking, thanks #bruno for the help btw
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <cs50.h>
int main(int argc, string argv[])
{
if (argc == 2)
{
char *endp;
long lkey;
int errno = 0;
lkey = strtol(argv[1], &endp, 10);
if ((errno != 0) || (*argv[1] == 0) || (*endp != 0) || (lkey < 0) || (((int) lkey) != lkey) || argc >= 3)
// this statement check to see the characters on argv[1] are all digits
{
printf("Usage: ./caesar key\n");
}
else // if the argv[1] is all digits then prompts the user for a plaintext
{
int key = atoi(argv[1]); // converts the key into an integer
string plaintext = get_string("plaintext: ");
printf("ciphertext: ");
for (int i = 0, n = strlen(plaintext); i < n; i++)
// goes thru each of the chars in plaintext and determines if is uppercase, lowercase or none.
{
if (isupper(plaintext[i]))
{
printf("%c", (((plaintext[i] + key) - 65) % 26) + 65);
// it takes 65 and then sums it back to convert the character from the ASCII uppercase index and back
}
else if (islower(plaintext[i]))
{
printf("%c", (((plaintext[i] + key) - 97) % 26) + 97);
// if the case is lower it takes 97 and then adds 97 back just to maintain the ASCII index.
}
else // if it not a lower case nor an uppercase, which means is a symbol then leave it like that.
{
printf("%c", plaintext[i]);
}
}
printf("\n");
return 0;
}
}
else if (argc == 1) // if the user doesnt prompt a key print no key to user
{
printf("NO KEY\n");
return 1;
}
else if (argc >= 3) // if user prompts 3 or more keys into argv then prints error message regargind the usage
{
printf("Usage: ./caesar key\n");
return 1;
}
}
if the user runs the command line like this for example, ./caesar 8x the program will run eventhough the user typed a letter
you get the number from the program argument using atoi, it stops when a non-digit is reached, so the result is the same for 8 and 8x.
If the argument is not compatible with a number since the beginning, atoi will silently return 0, this is why atoi is dangerous. Anyway here you cannot be in that case thank to your test isdigit(*argv[1]) before.
any ideas on how to iterate thru the user command argument and if there is a letter the program should run
Probably you wanted to say the program should not run.
To check if all the argument is compatible with a number you can use strtol to do the conversion :
if (argc == 2) {
char * endp;
long lkey;
errno = 0;
lkey = strtol(argv[1], &endp, 10);
if ((errno != 0) || (*argv[1] == 0) || (*endp != 0) ||
(lkey < 0) || (((int) lkey) != lkey)) {
printf("invalid argument %s\n", argv[1]);
}
else {
int key = (int) lkey;
...
Because you currently use an int for the key I check if the value is compatible with an int, I also check it is positive because this is compatible with your use and in your version you verify the first character is a digit
Good evening,
I don't know if I understood your question right, but I will give it a try.
The problem is that you are checking if the args counter is 2, and the 2nd argument is a digit. if so, do the following code you have written. but if not?
You are not handling the case of argc==2 and argv[1] is not a digit.

How do I write code to deny a non-numeric input using I've tried argv[1][i] it doesn't seem to work

I don't know where did I do wrong here my first time trying to use
int main(int argc, argv[])
I just don't know if I'm using it correctly.
Here's some code:
The code compiles properly and does give me the result correctly as well
encrypts "BaRFoo" as "FeVJss" using 4 as key
Log
running ./caesar 4...
sending input BaRFoo...
checking for output "ciphertext: FeVJss\n"...
BUT here is my problem: when I type in 2r or 4w or any non numeric key, I get this message:
:( handles non-numeric key
timed out while waiting for program to exit
Can anyone tell me how to add another loop for this function to get the numerical key to print as -Usage: ./caesar key . please ? Thanks very much an appreciate your help and useful advice.
#include <string.h>
#include <cs50.h>
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
// Variable declarations
bool keySuccessful = false;
int key = 0;
int input_length = 0;
string text = "" ;
// The number of command line args submitted was incorrect.
do
{
if(argc != 2)
{
printf("Usage: ./caesar key .\n");
return 1;
}
else
{
// Convert ASCII char to a alphabate char
// Access individual char in the string plain
// get the key val and converted to integer
key = atoi(argv[1]);
keySuccessful = true;
printf("%s",argv[1]);
}
}
while(!keySuccessful);
// get user input
text = get_string("%s",text);
printf("ciphertext: ");
input_length = strlen(text);
for (int i=0; i<input_length; i++)
{
// Checking if is the lowercase a=97 to z=112
// Print out lower case with ky a=65 to z=90
if(isupper(text[i]))
{
printf("%c", (((text[i] - 65) + key) % 26) + 65);
}
else if(islower(text[i]))
{
printf("%c", (((text[i] - 97) + key) % 26) + 97);
}
else
{
printf("%c", text[i]);
}
}
printf("\n");
return 0;
}
If I understood your question correctly, you want to make sure the first argument is a number, and stop execution otherwise.
You could easily do that looping over the argument, and checking the output of isdigit() from ctype.h for each character, like this:
int argumentIsNumber = 1;
for (int i = 0; i < strlen(argv[1]); i++) {
if (isdigit(argv[1][i]) == 0) {
argumentIsNumber = 0;
}
}
if (argumentIsNumber == 0) {
// Tell the user the introduced key is wrong
}
As you see in this example, you could set a flag to 1, and change it to 0 whenever a non-digit is found. Then, when the loop ends, you only have the flag to 1 if all the characters were digits.
Hope it helps!
you should make code if user prompt somethink like
$ ./caesar 20x
you shoud print
Usage: ./caesar key
for (int i = 0 , n = strlen(argv[1]); i < n; i++)
{
if (isalpha(argv[1][i]))
{
printf("Usage: ./caesar key\n");
return 1;
}
}

Resources