2

This code is supposed to work as a vigenere cipher. When run, however, no matter what input you put in, it segmentation faults. I'm writing this for the online CS50 course on edx. Isn't strncpy supposed to stop segmentation faults from happening if I tell it to copy over the right amount of characters?

#include <stdio.h>
#include <stdlib.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>

int main(int argc, string argv[]) {
    int result;

    if (argc != 2) {
        printf("Shame on you!\n");
        return 1;
    }

    string key = argv[1];
    string text = GetString();
    string cpy_key = NULL;

    //strncpy(cpy_key, key, strlen(key));
    for (int i = 0, n = strlen(text); i < n; i++) {
        strcat(cpy_key, key);
    }
    cpy_key[strlen(text)] = '\0';
    // Main loop starts here
    for (int i = 0, n = strlen(text); i < n; i++) {
        result = text[i] + cpy_key[i];

        if (isupper(text[i]) && (result > 'Z')) {
            result = result - 26;
        }
        if (islower(text[i]) && (result > 'z')) {
            result = result - 26;
        }
        if (isalpha(text[i])) {
            printf("%c", result);
        } else {
            printf("%c", text[i]);
        }
    }
    printf("\n");
    return 0;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Fluffy
  • 731
  • 1
  • 8
  • 18
  • 1
    Why are you using `strncpy()`? use `strcpy()` or if you know the destination buffer length and the source buffer length for sure use `memcpy()` also, don't use `strlen()` in a loop unless the string changes inside the loop. – Iharob Al Asimi Jan 24 '16 at 01:49
  • @iharob Using strcpy() still results in a segmentation fault. Also, strlen() is only being called once, when the loop starts, and is being saved as int n. – Fluffy Jan 24 '16 at 01:52
  • Do it in the line befor the llp starts, it looked to me like it was the `for` conditional. Are you on Linux? Or mac? If so I recommend [valgrind](http://www.valgrind.org), it should help you spot the problem quickly. – Iharob Al Asimi Jan 24 '16 at 01:56
  • `cpy_key = NULL` : Can't copy to `NULL`. – BLUEPIXY Jan 24 '16 at 01:57
  • You're right about that, but removing that stops it from compiling. – Fluffy Jan 24 '16 at 01:58
  • 2
    Also, what is *string*? Don't creat a type called *string* because it's very misleading. – Iharob Al Asimi Jan 24 '16 at 01:58
  • 1
    @Fluffy And confusing. – Iharob Al Asimi Jan 24 '16 at 01:58
  • @iharob `typedef char *string;` [cs50.h](https://mirror.cs50.net/library50/c/cs50-library-c-3.0/cs50.h) – BLUEPIXY Jan 24 '16 at 02:01
  • 2
    @BLUEPIXY Interesting, in my opinion it's still a bad idea. – Iharob Al Asimi Jan 24 '16 at 02:02
  • @iharob So, I need to allocate space for the cpy_key with malloc? – Fluffy Jan 24 '16 at 02:07
  • Did you know about `free()`? I don't see any in your code. And your `strcat()` is being misused too. – Iharob Al Asimi Jan 24 '16 at 02:08
  • @iharob Not really. I'm teaching myself as I go. All the stuff I found online used strcat to append a hardcoded string to another string. I couldn't find any other way to do it with a string variable. – Fluffy Jan 24 '16 at 02:12

4 Answers4

3

The cs50.h header defines typedef char *string;.

The core dump occurs because you're copying to a null pointer:

string cpy_key = NULL;

//strncpy(cpy_key, key, strlen(key));
for (int i = 0, n = strlen(text); i < n; i++) {
    strcat(cpy_key, key);

Whether it is strcat() or strncpy(), you need to allocate storage space for cpy_key. With the loop shown, if the string entered is 50 characters, you are copying the string 50 times over, so you'd need to allocate more than 2500 characters to be safe. Using strncpy() would do the job correctly — as long as you allocate enough space.

Note that strncpy() not a good function to use. If you have a 20 KiB buffer and copy a 10 byte string into it, it writes 20470 null bytes after the string. If you have a 50 byte buffer and you copy 75 bytes into it, it copies the 50 bytes and does not leave the buffer null terminated. Neither is obvious; the lack of guaranteed null termination makes it dangerous. There are worse interfaces (strncat() is the prime candidate — what does the length parameter represent?) but not many.

You have some work to do on your encryption algorithm.

You could look at Vigenere cipher only works up to until dealing with a space in C — why? to see how else it could be done.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
1

One problem is memory ie

string cpy_key = NULL;

does not allocate memory ie it's just a name with no size. Knowing this then this must fail

strcat(cpy_key, key);

In that call you tried to concatenate a thing that has a size to a thing with no size.

Harry
  • 11,298
  • 1
  • 29
  • 43
0

You do not need to make a copy of key, you can just index into key modulo its length.

By the way, you should NEVER EVER USE strncpy, it does not do what you think it does, its semantics are error prone, it is never the right tool for the job. The last argument is supposed to be the size of the destination array, but if the source is too large, the destination will not be null terminated and if the size is small and the destination large, this function will waste time padding the whole destination array will '\0' bytes. Not using strncpy will save you from many errors.

Also it is unfortunate that cs50.h defines string with typedef char *string;. Using such a type is misleading and error prone, especially for people who know C++. Just use char *, it makes your code much easier to read by C programmers. I hope you are not required to use this type, hiding pointers behind typedefs is generally not a good idea.

Here is a simpler version:

#include <stdio.h>
#include <stdlib.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>

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

    if (argc != 2) {
        printf("Shame on you!\n");  // you might want to be more explicit ;-)
        return 1;
    }

    char *key = argv[1];
    int klen = strlen(key);
    char *text = GetString();
    if (!text) {
        /* end of file or read error */
        return 1;
    }

    // Main loop starts here
    for (int i = 0, n = strlen(text); i < n; i++) {
        result = text[i] + key[i % len];

        if (isupper((unsigned char)text[i]) && result > 'Z') {
            result = result - 26;
        }
        if (islower((unsigned char)text[i]) && result > 'z') {
            result = result - 26;
        }
        if (isalpha((unsigned char)text[i])) {
            printf("%c", result);
        } else {
            printf("%c", text[i]);
        }
    }
    printf("\n");
    return 0;
}

HINT: your vigenere is incorrect, you should use result = text[i] + key[i % len] - 'A'; if the key is all uppercase.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • This doesn't work. It prints a series of unprintable characters. – Fluffy Jan 24 '16 at 02:18
  • @Fluffy: I fixed the crash, you should fix the logic, I cannot guess what your decyphering method is supposed to be, although I know *vigenere*, but you seem to have done it wrong. – chqrlie Jan 24 '16 at 02:20
  • @Fluffy: I think I found the problem with the *vigenere* algorithm, did you? – chqrlie Jan 24 '16 at 02:29
  • Yep, I did. Thanks for you help. – Fluffy Jan 24 '16 at 02:47
  • @chqrlie: I beg to differ. It is true that strncpy does not guarantee to NUL-terminate the result; however, this doesn't make the function error prone. It is the programmer's responsibility to make sure a string is NUL-terminated when using strncpy, or strcpy. As for the semantics and the behavior of the strncpy function, could you please explain more? I think you don't know what you are talking about.. – Ben cisneros Jan 24 '16 at 03:47
  • I edited this answer to correct the types and fix the cipher error. – John Hascall Jan 24 '16 at 05:58
  • The `typedef char* string;` line is provided by [cs50.h](https://mirror.cs50.net/library50/c/library50-c-6/cs50.h.src), so unfortunately the bad practice will remain so long as the C version of the CS50 library continues to do this. While the student could use `char *`, it's possible that `string` is required per convention; i.e. using `char *` could result in a lower mark despite the fact that `string` and `char *` are the same. –  Jan 24 '16 at 06:32
  • @Bencisneros: yes I **do** know what I'm talking about! `strncpy` is misused by so many programmers, beginners and not, that is most definitely does qualify as error prone. As for its semantics, read JonathanLeffner's response and many more SO posts. The OP obviously does not know what this functions does, calling it `strncpy(cpy_key, key, strlen(key));` is one of many symptoms. It is common for beginners to make this kind of mistake, no offense, the rather strong warning is not judgmental, it is friendly advice. – chqrlie Jan 24 '16 at 08:17
  • @JohnHascall: please do not make so many changes in someone else's answer, write your own. I do mean everything I wrote and I do not agree with most of your changes. Why did you change the program's semantics? Reading the chars into an `int` before passing that to `islower` does not fix the signed char issue at all, the char is just sign extended and `islower` has undefined behavior on negative values different from `EOF`. I shall revert these changes. – chqrlie Jan 24 '16 at 08:22
  • Why would you purposely give a broken answer? – John Hascall Jan 24 '16 at 08:47
  • @JohnHascall: It was a quick answer to deter the OP from using `strncpy` and let him focus on his Vigenere implementation. I later provided extra help, and he did fix that too. – chqrlie Jan 24 '16 at 09:06
  • @chqrlie: just letting you know that my comment was broken for some reason. I agree with the fact that strncpy is misused by many; one can even find its misused in many programming books. However, I will still disagree with your "Never use" comment. Using strncpy is helpful, but is not a guarantee of safety. You still have to be careful about NUL-termination issues when the source string is longer than, or equal in length to, the destination string. – Ben cisneros Jan 24 '16 at 09:40
  • @ChronoKitsune: such a requirement would be counterproductive. For what it's worth, the source code for cs50.c is here: mirror.cs50.net/library50/c/library50-c-6/cs50.c and the implementation of `GetString` does use `strncpy` where `memcpy` would have been more appropriate and a check for `malloc` failure is missing. – chqrlie Jan 24 '16 at 09:50
-1

Is this C or C++? In C you need to allocate space on your own for char arrays (strings):

char *cpy_key = 0 ;
...
size_t key_len = strlen (key) + 1 ; // adding space for terminating NULL
...
cpy_key = malloc ( key_len ) ; // add error management here
...
strncpy ( cpy_key , key , key_len ) ; // add error management here
...
free ( cpy_key ) ; // when you don't need it anymore
mauro
  • 5,730
  • 2
  • 26
  • 25
  • Note that you're treading on thin ice allocating `key_len` rather than `key_len + 1` bytes. The string so created will not be null terminated. Adding the `+ 1` allows you to null terminate the string. With the longer allocation, you no longer need to use `strncpy()` because you've then ensured you have enough space (`strcpy()` would be fine). You could also use `memmove()` (or even `memcpy()`) with a length of `key_len + 1`. – Jonathan Leffler Jan 24 '16 at 08:55
  • @JonathanLeffler you're (once more) right. Updating my answer right now. Thank you – mauro Jan 24 '16 at 11:18