Шифр Виженера в C

Я написал программу на C, которая принимает открытый текст и пароль и создает зашифрованный текст, используя шифр Виженера. Хотя в большинстве случаев код выдает правильный вывод, я нашел пример, в котором он выдает неожиданный вывод, и я не могу найти проблему самостоятельно. Выход такой:

jess@laptop:~/Desktop/programming/C/current/vigenere$ ./vigenere lemon attackatdawn
LXF OPV EFR NHR [0002]

Это поле в конце не отображается должным образом, оно предназначено для представления, когда bash пытается отобразить символ ascii 2, но копирование и вставка не показывают его правильно. Это пример текста из Википедии для шифра, и это единственный текст, который я нашел, который ломает мою программу (я не знаю, в чем причина, поэтому я не могу воспроизвести это), но я уверен, что есть еще строки это даст аналогичные результаты. Я подозреваю, что сделал что-то, что вызывает неопределенное поведение, но я не уверен. Что я здесь сделал не так? Мой код:

// vigenere.c - Takes a plaintext and a cipher key from argv[1] and argv[2] and produces the cipher text according to Vigenere's cipher

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

void string_clean(char *source) //Courtesy (mostly) of user 'Aaron' of StackOverflow
{
    char *i = source;
    char *j = source;
    
    while(*j != 0) {
        *i = *j++;
        if( *i != ' ' && (isupper(*i) || islower(*i)) )
            i++;
    }
    
    *i = 0;
}

char *vigenere_enc(char plain[], char cipher[])
{
    char *cipher_text;
    
    string_clean(plain);
    string_clean(cipher);
    
    int plain_len = strlen(plain);
    int cipher_len = strlen(cipher);
    
    if( !(cipher_text = calloc(plain_len, sizeof(char))) )
        return 0;
    
    for(int i = 0; i < cipher_len; i++) {
        if(isupper(cipher[i]))
            cipher[i] -= 'A';
        else if(islower(cipher[i]))
            cipher[i] -= 'a';
    }
    
    int j = 0;
    
    for(int i = 0; i < plain_len; i++, j++) {
        if(j == cipher_len)
            j = 0;
        
        if(isupper(plain[i]))
            plain[i] -= 'A';
        else if(islower(plain[i]))
            plain[i] -= 'a';
        
        cipher_text[i] = ((plain[i] + cipher[j]) % 26) + 'A';
    }
    return cipher_text;
}

int main(int argc, char *argv[])
{
    if(argc != 3)
        return 1;
    char *cipher = vigenere_enc(argv[2], argv[1]);

    for(int i = 0; i < strlen(cipher); i++) {
        if(i % 3 == 0 && i != 0)
            putchar(' ');
        putchar(cipher[i]);
    }
    
    putchar('\n');
    
    return 0;
}

Любая помощь/предложения приветствуются!


person jess    schedule 05.09.2015    source источник


Ответы (1)


Вам нужно NUL-терминировать вашу выходную строку. :-( Это также означает, что ваш вызов calloc (на самом деле вам следует просто использовать malloc) должен указывать plain_len + 1, а не только plain_len.

person Chris Jester-Young    schedule 05.09.2015
comment
OP также записывает входные строки в string_clean(), но аргументы, переданные в main(), могут быть недоступны для записи. - person Weather Vane; 05.09.2015
comment
Большое спасибо за этот ответ, поскольку он решил мою проблему. Однако два вопроса: зачем использовать malloc вместо calloc и почему plain_len + 1, а не plain_len? Не мой лучший день для одиночек. :/ - person jess; 05.09.2015
comment
calloc обнуляет память, но вы все равно записываете ее всю, не нуждаясь в том, чтобы она была равна нулю. И +1, потому что вам нужно место для упомянутого терминатора строки. strlen() не включает это. - person Weather Vane; 05.09.2015
comment
@psychedelic_alex malloc сначала не очищает память. Обычно это быстрее, чем calloc, и, поскольку ваша программа не использует очищенную память, она работает просто отлично. + 1 потому, что вам нужно выделить дополнительный символ для терминатора NUL. Или, знаете, то, что только что сказал Флюгер. ;-) - person Chris Jester-Young; 05.09.2015
comment
Большое спасибо, ребята, вы сэкономили мне кучу времени здесь :-). Еще один вопрос для @WeatherVane, когда и почему argv не будет доступен для записи? Я на самом деле слышал, что это всегда доступно для записи, но я рад ошибиться в этом. - person jess; 05.09.2015
comment
@psychedelic_alex, возможно, вы правы, здесь обсуждается изменение аргументов stackoverflow.com/questions/25737434/is-argvn -writable Я написал может быть не из осторожности. Конечно, аргументы, находящиеся в стеке, такие как argc, доступны для записи. - person Weather Vane; 05.09.2015