For the CS50x Problem Caesar I have created a program that encrypts messages using Caesar’s cipher.
For this, the program must recognize that a command-line argument is only a number. So no two or more numbers, no number below zero, and no text.
But as soon as I add the check if it is a text with || isalpha(argv[1]), the program does not work anymore.
The terminal prints the following when I try to run the program:
Segmentation fault
Can anyone tell me what is the problem with the code
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
int main(int argc, string argv[])
{
int kkey = 0;
// Check if correct command-line arguments
if (argc != 2 || atoi(argv[1]) < 0 || isalpha(argv[1])) //segfault here
{
printf("./caesar key\n");
return 1;
}
else
{
kkey = atoi(argv[1]);
}
// Ask for Plaintext to encrypt
string plaintext = get_string("plaintext: ");
for (int i = 0, n = strlen(plaintext); i < n; i++)
{
if (isalpha(plaintext[i]) && islower(plaintext[i]))
{
plaintext[i] = (plaintext[i] - 'a' + kkey) % 26 + 97;
}
else if (isalpha(plaintext[i]) && isupper(plaintext[i]))
{
plaintext[i] = (plaintext[i] - 'A' + kkey) % 26 + 65;
}
printf("%c", plaintext[i]);
}
printf("\n");
return 0;
}
Thank you very much for your help.
As said by #Gerhardh, you can't use strings as argument of isalpha, you need a loop to check each character of the string.
In any case that is not the best approach, using a negated isdigit would be a better option, because it accounts for all the other non numeric characters.
//...
// Check if correct command-line arguments
if (argc != 2 || atoi(argv[1]) < 0)
{
printf("./caesar key\n");
return 1;
}
for(size_t i = 0; i < strlen(argv[1]); i++){
if(!isdigit(argv[1][i])){ //if one of the characters is not a digit 0-9
puts("./caesar key\n");
return 1;
}
}
kkey = atoi(argv[1]); //no else needed
//...
Note that atoi will invoke undefined behavior if the converted value is not representable by an int.
You can use strtol for a more robust alternative.
The link is for Linux man page which I find quite nice, but this is cross-platform.
Again, as stated by #Gerhardh, using character codes may backfire, in this case you are using ASCII encoding, but there are others, this makes your code less portable, use the character instead, 26 + 'a' and 26 + 'A'.
Related
#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?
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;
}
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h> //covert str into int atoi()
#include <ctype.h> // isalpha(), isdigit() etc.
#include <string.h> //strlen() etc.
int main(int argc, string argv[])
{
// convert argv[1] into int
int key = atoi(argv[1]);
// if input is 2 command lines and argv > 0 and there is no alphabet n shit in argv[1] we good to go
if (argc == 2 && key > 0 && isdigit(argv[1]))
{
//ask for input to chiper
string plain = get_string("Plaintext: ");
int len_plain = strlen(plain);
// check each char in string plain
for (int i = 0; i < len_plain; i++)
{
// if its from 'a' to 'z' add number of key
if (plain[i] >= 'a' && plain[i] <= 'z')
{
char cipher = (plain[i] + key) % 26;
printf("%c", cipher);
return 0;
}
}
}
else
{
printf("Usage: ./caesar kay");
return 1;
}
}
When running this code, I'm faced with a segmentation fault. What did I do wrong? I have been working a few days on this code but I cannot make it work.
This isdigit(argv[1]) is giving a seg fault. The function signature of isdigit (from the man page):
int isdigit(int c);
But, argv[1] (if it exists!) is a string. Suggest you follow the spec by
verifying the right number of command line arguments is
entered
checking that each char in said argument is a decimal digit
Only then are you assured that atoi will give the desired result.
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.
#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
int main(int argc, string argv[])
{
string k = argv[1];
string s = GetString();
int l = strlen(k);
for(int i = 0, n = strlen(s); i < n; i++)
{
if(s[i] >= 65 && s[i] <= 90)
{
int i2 = ((s[i]-65) + (k[i%l]-97)) % 26;
printf("%c", i2+65);
} else if(s[i] >= 97 && s[i] <= 122)
{
int i2 = ((s[i]-97) + (k[i%l]-97)) % 26;
printf("%c", i2+97);
} else
{
printf("%c", s[i]);
}
}
printf("\n");
return 0;
}
I have removed as many parts as I can in order to make the code more relevant to the question. Basically why does this code work when "s" does not have any space(" ") in it and doesn't when "s" consists of space(" ")?
As most of you may know the idea is the argument entered at argv[1] is the "keyword" for the cipher. User then inputs a "plain-text" to cipher (s). It works when I try with various words or sentences if it doesn't include any space, " ". I just don't understand the logic behind this. Why does the cycle break if s[i] is not one of the first two conditions - I would have thought that "else" condition would work.
I would really appreciate it if someone can shed some light on this - many thanks in advance!
ps: I know there are some extra libraries at the top and the user input at argv[1] is not verified via isalpha(). I just want to understand the cycle process better for now, I have those checks in another file ready.
Here is code that implements the 'separate counters for string and key' comment that I made. It also uses the letter codes 'a' and 'A' (and avoids needing to use 'z' or 'Z') instead of using numbers. It does assume that you are dealing with a single-byte code set (not UTF-8 unless you're working in the ASCII range) where the lower-case and upper-case letters are each in a contiguous range (so it won't work reliably with EBCDIC, but will with most other code sets), and it also ignores accented characters. (It would have to do setlocale("") to get locale-specific interpretations of which characters are letters.)
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int main(int argc, string argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s key\n", argv[0]);
return 1;
}
string k = argv[1];
int l = strlen(k);
for (int i = 0; i < l; i++)
{
int c = k[i];
if (!isalpha(c))
{
fprintf(stderr, "%s: non-alpha character %c in key string\n", argv[0], c);
return 1;
}
k[i] = tolower(c);
}
printf("Enter a string to be encrypted:\n");
string s = GetString();
int n = strlen(s);
for (int i = 0, j = 0; i < n; i++)
{
int c = (unsigned char)s[i];
if (isupper(c))
c = ((c - 'A') + (k[j++ % l] - 'a')) % 26 + 'A';
else if (islower(c))
c = ((c - 'a') + (k[j++ % l] - 'a')) % 26 + 'a';
putchar(c);
}
putchar('\n');
return 0;
}
Here is a sample run that demonstrates the weakness of using 'a' as one of the letters in the key for this Vigenere cipher:
./vc caesArandAbrAcaDabRa
Enter a string to be encrypted:
It is reported that Caesar said "Veni, vidi, vici" when he conquered Britain.
Kt mk rvpbutfu tjaw Cbvscr wsiu "Vrqi, wzdk, vlcj" nhgn lw cfndxesvd Drltbzn.