I'm trying to create a program that accepts cmd line arguments to encipher a plaintext!
The program must accept one cmd line argument after its name when making it and this would be the key which by the plaintext (only) alphabetical characters are rotated by this key (e.g. it's number is added to the real alphabet ASCII number resulting in another alphabet to be printed!
it is supposed to print an error message when one argument is present (e.g. here:/make encipher)
instead of here:/make encipher 12 <-- 12 = key!
I am getting a segmentation fault when running the program without the key argument, why?
This is the full code. I'm posting it because I need to learn where is my fault's exact location
and why is it triggered?!
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h> // To use atoi (converting a string to an int)
#include <ctype.h>
#include <string.h>
bool key_is_numb(string argv[]);
void encipher(string txt, int key);
int main(int argc, string argv[])
{
if (key_is_numb(argv) == false)
{
printf("Usage: ./caesar key\n");
return 1;
}
else
{
int key = atoi(argv[1]);
string plaintext;
if (argc == 2 && key > 0)
{
plaintext = get_string("plaintext: ");
encipher(plaintext, key); // A function that prints the ciphered text
return 0; // returns Zero as main return value which means "All good"!
}
else if (argc == 1 || argc > 2 || key <= 0)
{
printf("Usage: ./caesar key\n");
return 1;
}
} // End else.
} // End main()å func.
bool key_is_numb(string argv[])
{
int n = strlen(argv[1]);
for (int i = 0; i < n; i++) // checking element by element in the second string of the argv[] array of strings
{
if (isdigit(argv[1][i]) == 0) // if the entered string "key" contains chars other than digits.
{
return false; // break out of the if statement & the entire function key_is_numb()
// and return false as soon as a letter is encountered.
}
else
{
continue; // go up & start the next iteration for the for loop.
}
// if only digits encountered then this for loop will come to an end and exist from here.
} // End for loop
return true; // function exits and return boolean true from here.
} // End key_is_numb() func.
void encipher(string txt, int key)
{
printf("ciphertext: ");
for (int i = 0, n = strlen(txt); i <= n; i++) // strlen counts the number of elements in a string excluding '\0'
{
char c = txt[i];
if (isalpha(c))
{
if (isupper(c))
{
char m = 'A'; // This is a modifyer character equals to 'A' = 65 so that it is indexed # ZERO!
printf("%c", (c - m + key) % 26 + m );
//c = ((((int)txt[i] - 65) + key) % 26) + 65; // char c = 65 <-- 65 is an ASCII code equals 'A'
}
else if (islower(c))
{
char m = 'a'; // This is a modifying character 'a' = 97
printf("%c", (c - m + key) % 26 + m );
}
}// End if(alpha).
else
{
printf("%c", c);
}
} // End for().
printf("\n");
} // End encipher() func.
int n = strlen(argv[1]);
in key_is_numb() and
int key = atoi(argv[1]);
in main().
If you didn't enter a key argument, argv[1] as equal as argv[argc] is a null pointer as stated in C17, §5.1.2.2.1/2.
Any attempt to access its data is undefined behavior and probably caused the segmentation fault.
Well you are assuming that argv[1] is defined in key_is_numb. However, in C and C++, the second parameter of the main function contains command line arguments. Which, in your case will be the name of the binary as the first element, then any other arguments. This is why when you are running the program without arguments, it will segfault, as there are no argument to put in argv, and no default value either.
You should always check the size of argv, by using the number stored in argc, before trying to read anything in argv.
Your segmentation fault comes from this line int n = strlen(argv[1]);, but I'd highly suggest you to learn to use debugger software like valgrind, which if the program has been compiled with debug flag will tell you the exact line.
Other debugger are really useful too, so you should learn to use them, as they usually report this kind of errors.
Your code asumes there is always an argv[1]. You should check argc which tells the number of arguments. For example:
int main(int argc, string argv[])
{
if (argc < 2) {
printf("Key required\n");
exit (1);
}
Related
I am working through a programming assignment. I had the key validation working within main but decided to try to make it a separate function. I do not understand functions very well yet so I am unable to see where I am going wrong. Whenever I run the program, I always just get "Key is Valid" even when I know it's not. As I said, the program was running fine in main.
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
int validate (int c, string v[]); //prototpe validate function
int main (int argc, string argv[])
{
int validate (int argc, string argv[]); //run validate for argc and argv
printf("Key is valid\n"); //if valid, print message
}
int validate (int c, string v[])
{
//Validate that only one Command Line Argument was entered
if (c != 2) //Check the number if entered commands at execution
{
//If more than one, print error message
printf("Key must be the only Command Line Argument\n");
return 1; //Return false
}
//Validate that Key length is 26 characters
if (strlen(v[1]) != 26) //Don't forget null 0
{
printf("Key must contain exactly 26 characters\n");
return 1; //Return false
}
//Validate that all Key characters are alphabetic
for (int i = 0, n = strlen(v[1]); i < n; i++)
{
//Check each element of the array
if (isalpha(v[1][i]))
{
continue; //Continue if alphabetic
}
else
{
//if non-alphabetic, print error code
printf("Key must contain only alphabetic characters\n");
return 1; //Return false
}
}
//Validate that each Key character is unique
for (int x = 0, z = strlen(v[1]) - 1; x < z; x++)
{
//Create a second loop to compare every element to the first before incrementing the first
for (int y = x + 1; y < z; y++)
{
//Cast array elements to int, check if each element equals next element in array
if (v[1][x] == v[1][y])
{
printf("Key must contain exactly 26 unique characters\n");
return 1;
}
}
}
return 0; //Key is valid, so return true
}
You are just declaring the function validate instead of running that and printing Key is valid unconditionally.
To run the function validate and print Key is valid only if it returns 0, the main function should be like this:
int main (int argc, string argv[])
{
if (validate (argc, argv) == 0) //run validate for argc and argv and check its response
{
// put printing inside if statement so that it runs only if the condition is true
printf("Key is valid\n"); //if valid, print message
}
}
Function declaration hints the compiler that I will return a particular data type and I will accept the given data types in the arguments section. For example,
int check(int a, int b); -> return type is int and the function will accept 2 integer parameters.
int mul(int a, float b); -> return type is int and the function will accept 1 integer parameter and 1 float.
void check(); -> returns nothing, accepts nothing
function calling is like your code is calling the function to execute.
int c = check(2, 3);
int b = mul(3, 0.12);
check();
Your function is returning some value. You have to get that value and execute rest of the code based on the value as below.
if (validate (argc, argv) == 0) {
printf("key is valid");
} else {
printf("key is not valid");
}
I've implemented the Caesar's cipher in C, and, despite the algorithm is working, I didn't understood why (sometimes) I get an empty value if I do not subtract the first letter of the alphabet before adding the key. Here's the full code (see line 59 or search for return (letter + k) % 26):
#include <cs50.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
string caesar_cipher(string text, int k);
char replace_letter(char letter, int k);
bool is_numeric(string input);
int main(int argc, string argv[])
{
if (argc != 2 || (argc == 2 && !is_numeric(argv[1])))
{
fprintf(stderr, "You must specify a key to the cipher! Exiting...\n");
exit(EXIT_FAILURE);
}
// Convert command line argument to integer.
int k = atoi(argv[1]);
// Prompts user for the text to encrypt
string text = get_string("plaintext: ");
// Returns encrypted text
printf("ciphertext: %s\n", caesar_cipher(text, k));
exit(EXIT_SUCCESS);
}
string caesar_cipher(string text, int k)
{
int text_length = strlen(text);
string ciphered_text = text;
for (int i = 0; text[i] != '\0'; i++)
{
ciphered_text[i] = replace_letter(text[i], k);
}
return ciphered_text;
}
char replace_letter(char letter, int k)
{
// Early return when 'letter' is a non-alphabetical character
if (!isalpha(letter))
{
return letter;
}
char operation_letter = 'a';
if (isupper(letter))
{
operation_letter = 'A';
}
// return (letter + k) % 26; // Sometimes, returns an empty value
return ((letter - operation_letter + k) % 26) + operation_letter;
}
// Loop over characters to check if each one of them is numeric
bool is_numeric(string input)
{
for (int i = 0; input[i] != '\0'; i++)
{
// If character is not numeric
// returns false.
if (isdigit(input[i]) == 0)
{
return false;
}
}
return true;
}
Can anybody explain why this happens?
You need to account for the first letter of the alphabet (either a or A) in your functions because chars are internally represented as an integer number (usually only a single byte, but it depends on the encoding). In ASCII for example, doing a % 26 will result in any of the 26 first values of the ASCII table, none of which are actual letters. Hopefully I made myself clear.
I have another question about my caesar code-enchipering programs. Here I specified, that I want to take only integer. But somehow it proceed when 2x is inputted in the command line arguments.
Below is the full code... I deeply appreciate it if anyone can help me answer my question.
#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <math.h>
#include <ctype.h>
#include <stdlib.h>
//one command line argument with the type int
int main(int argc, string argv[])
{
//main program
if (argc == 2 && isdigit(*argv[1]))
{
int k = atoi(argv[1]); //get Caesar key into a variable
string pltext = get_string("plaintext: "); //getting input for the plain text
char cptext[strlen(pltext) + 1];
for (int i = 0, n = strlen(pltext) ; i < n; i++) //turning pltext to integer
{
if (pltext[i] >= 'a' && pltext[i] <= 'z')
{
cptext[i] = ((pltext[i] - 'a' + k) % 26) + 'a'; //shifting the integer with k (lowercase)
}
else if (pltext[i] >= 'A' && pltext[i] <= 'Z')
{
cptext[i] = ((pltext[i] - 'A' + k) % 26) + 'A'; //shifting the integer with k (uppercase)
}
else
{
cptext[i] = pltext[i]; //other symbol stays
}
}
//print out result
printf("ciphertext: %s\n", cptext);
return 0;
}
//setting condition that : K = + ; if more or less than one, immediate error message, return 1
//if not decimal return = usage ./caesar. key, return value 1 to main
else if (argc != 2)
{
printf("Error 404 : \n");
return 1;
}
else
{
printf("Usage: ./caesar key\n");
return 2;
}
}
The isdigit function only checks the first digit. meaning if I use 2x, itll see 2. what you could do is put a loop, checking the digits in the key given by the user, and see if each one is a digit!
argv[ ] in your program is char * (acc to cs50 header), so first element is a pointer to array of char's , in C programming language if you de-reference a pointer to array you get the first element in your array.
so in your program when you de-reference pointer to string "2x" ex : *argv[1], you get first element of that string (array of chars) which is 2 ! so this returning true (or 1 in this case ) and continuing that conditional statement :)
char * string = "2x";
if (string[0] == *string) print("this is the bug in your program!");
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;
}
I've been trying different solutions but am not sure where to look for the solution.
I prompt the user for "plaintext" and whatever their input is, the chars in their input need to be rotated alphabetically by a number (aka the key) which they provide.
For example: plaintext: HELLO would spit out ciphertext: IFMMP if the key were 1.
Assuming the key will always be a number, here is what my code looks like which attempts to rotate each char by Key: 1. I'm a real noob so please break it down is possible.
{
string s = get_string("plaintext: ");
printf("ciphertext: %s\n", s + 1);
}
The remaining code (which includes identifying and filtering out the key is:
#include <cs50.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
int main(int argc, string argv[])
{
int counter = 0;
if (argc == 2)
{
for(int k = 0; k <strlen(argv[1]); k++)
{
if (isdigit(argv[1][k]))
{
counter++;
}
}
if (strlen(argv[1]) == counter)
{
string s = get_string("plaintext: ");
if(s)
{
printf("ciphertext: %s\n", s + 1);
free(s);
}
}
else
{
printf("Usage: ./caesar key\n");
}
}
else
{
printf("Usage: ./caesar key\n");
}
}
Any assistance would be appreciated.
I do not have cs50, so cannot test everything in your code, but addressing your primary question: How Do I Rotate A Char Alphabetically By Integer X, the code section you have identified as where the rotation occurs:
if (strlen(argv[1]) == counter)
{
string s = get_string("plaintext: ");
printf("ciphertext: %s\n", s + 1);
}
But this section doesn't really rotate the text. It rather attempts to print the string obtained from the call to get_string after adding 1 to it. First, this is not modifying s at all. Second, s + 1 is not a legal operation. To rotate s, additional code is needed between those two lines.
If I understand get_string(), it is essentially a combination of printf(), fgets(,,stdin) and strdup().
So after the call to that function you are left with s which will be populated with whatever the user typed into stdin. For illustration, let
say s contains "what the user typed". How that string is represented in memory, including the NULL terminator, can be depicted as follows:
|w|h|a|t| |t|h|e| |u|s|e|r| |t|y|p|e|d|0|
To rotate this by X, as you have stated, each character needs to be modified by adding the value X, in this case 1
Showing ASCII equivalent values:
|119|104|97|116|32|116|104|101|32|117|115|101|114|32|116|121|112|101|100|0|
The rotated string then would be each value + 1:
For the general case, assume rotation value is read in from command line argument and placed into int n = 0;
earlier in code:
if(isdigit(argv[2][0]))
{
n = atoi(argv[2])
}
The rotation: (the following is complete. It may need a little debugging around the corner case [where values wrap around the end of ASCII values when added to n.] Lines are commented indicating where I used ANSI C instead of CS50. [I do not have CS50])
int main(int argc, char *argv[])//note: command line requires a numeric
// argument be used: prog.exe 12 (for n == 12)
{
//char s[80];//used to test (I do not have cs50)
char *rotated = NULL;
int i;
if(argc != 2)
{
printf("Usage: prog.exe <positive int value.>\nExiting\n");
return 0;
}
if(isdigit(argv[1][0]) == 0)
{
printf("Usage: prog.exe <positive int value.>\nExiting\n");
return 0;
}
int n = atoi(argv[1]);
string s = get_string("plaintext: ");
// printf("Enter text to be rotated\n");//used to test (I do not have cs50)
// scanf("%[^\n]", s);
// string rotated = strdup(s); //preserve s by manipulating identical string
rotated = StrDup(s);
if(rotated)
{ //advance value by n
int origVal = 0;
int len = strlen(rotated);
for(i=0;i<len;i++)
{
origVal = rotated[i];
if((rotated[i] + n) <= 127)
{
rotated[i] += n;
}
else
{
rotated[i] = abs(127 - (origVal + n));
if(rotated[i] == 0) rotated[i] += 23; //skip to printable characters (http://facweb.cs.depaul.edu/sjost/it212/documents/ascii-pr.htm)
//if(rotated[i] == 0) rotated[i] += 1; //skip only ASCII NULL value
}
}
rotated[i] = 0; //adding NULL to end of string
printf("ciphertext: %s\n", rotated);
free(rotated);
}
return 0;
}
You can achieve this with typecasting:
char input = 'f';
char output = (char)((int)input + 1);
You should also handle edge cases, i.e. when the character value is near the end of the alphabet.