Не уверен, почему toupper() обрезает последнюю букву в C

#arrays #c #letter #toupper

Вопрос:

Таким образом, цель этой программы состоит в том, чтобы в основном взять 26-буквенный «ключ» в терминале (сквозной argv[] ) и использовать его индекс в качестве ориентира для замены. Таким образом, в терминале есть 2 входа, которые вы вводите, один в argv[] , и один-это просто обычный get_string() вход. argv[] Ввод будет выглядеть следующим образом: ./s YTNSHKVEFXRBAUQZCLWDMIPGJO где s находится имя файла. И тогда get_string() входные данные будут выглядеть так: plaintext: HELLO . (Входные данные есть HELLO ). Затем программа выполнит перебор всех букв в вводе открытого текста и заменит его алфавитный индекс в соответствии с индексом argv[] ключа. Например, H имеет алфавитный указатель 7 (где a = 0 и z = 25), поэтому мы смотрим на 7-й индекс в ключе YTNSHKV(E)FXRBAUQZCLWDMIPGJO , который в данном случае является E . Он делает это для каждой буквы на входе, и в итоге мы получим результат ciphertext: EHBBQ . Вот как это должно выглядеть в терминале:

 ./s YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext:  HELLO
ciphertext: EHBBQ
 

Но мой вывод таков EHBB , так как он по какой-то причине отсекает последнюю букву, когда я использую toupper() .

Кроме того, прописные и строчные буквы зависят от ввода открытого текста, если бы ввод открытого текста был hello, world , а argv[] ключ был YTNSHKVEFXRBAUQZCLWDMIPGJO , вывод был бы jrssb, ybwsp , и если бы ввод был HellO, world с тем же ключом, вывод был бы JrssB, ybwsp .

Я в основном покончил с проблемой, моя программа заменяет открытый текст, указанный в правильном зашифрованном тексте, на основе ключа, который был введен через командную строку. Прямо сейчас, скажем , если ввод открытого текста был HELLO , и ключ был vchprzgjntlskfbdqwaxeuymoi (все в нижнем регистре), то он должен вернуться HELLO , а не hello . Это связано с тем, что моя программа помещает все буквы в ключе командной строки в массив длиной 26, и я перебираю все буквы открытого текста и сопоставляю значение ascii (минус определенное число, чтобы перевести его в диапазон индексов 0-25) с индексом в ключе. У So E есть алфавитный индекс 4 , так что в этом случае моя программа будет строчной p , но мне это нужно P , поэтому я использую toupper() .

Когда я использую tolower() , все работало нормально, и как только я начал использовать toupper() , последняя буква ciphertext по какой-то причине обрезается. Вот мои выходные данные перед использованием toupper() :

 ciphertext: EHBBQ
 

И вот мой вывод после того, как я использую toupper() :

 ciphertext: EHBB
 

Вот мой код:

 int main(int argc, string argv[]) {
    string plaintext = get_string("plaintext: ");
    
    // Putting all the argv letters into an array called key
    char key[26]; // change 4 to 26
    for (int i = 0; i < 26; i  ) // change 4 to 26
    {
        key[i] = argv[1][i];
    }
    
    // Assigning array called ciphertext, the length of the inputted text, to hold cipertext chars
    char ciphertext[strlen(plaintext)];
    
    // Looping through the inputted text, checking for upper and lower case letters
    for (int i = 0; i < strlen(plaintext); i  )
    {
        // The letter is lower case
        if (islower(plaintext[i]) != 0)
        {
            int asciiVal = plaintext[i] - 97; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            char l = tolower(key[asciiVal]); // tolower() works properly
            //printf("%c", l);
            strncat(ciphertext, amp;l, 1); // Using strncat() to append the converted plaintext char to ciphertext
        }
        // The letter is uppercase
        else if (isupper(plaintext[i]) != 0)
        {
            int asciiVal = plaintext[i] - 65; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            char u = toupper(key[asciiVal]);  // For some reason having this cuts off the last letter 
            strncat(ciphertext, amp;u, 1); // Using strncat() to append the converted plaintext char to ciphertext
        }
        // If its a space, comma, apostrophe, etc...
        else
        {
            strncat(ciphertext, amp;plaintext[i], 1);
        }
    }
    
    // prints out ciphertext output
    printf("ciphertext: ");
    for (int i = 0; i < strlen(plaintext); i  )
    {
        printf("%c", ciphertext[i]);
    }
    printf("n");
    printf("%cn", ciphertext[1]);
    printf("%cn", ciphertext[4]);
    //printf("%sn", ciphertext);
    return 0;
}
 

Комментарии:

1. Не уверен, повторяю ли я это, но «зашифрованный текст[strlen(открытый текст)];» выглядит подозрительно для меня. Я бы предпочел видеть «зашифрованный текст char[strlen(открытый текст) 1];», с 1 для размещения , который завершает строку.

2. По крайней мере: char ciphertext[strlen(plaintext)] -> char ciphertext[strlen(plaintext) 1]

3. Не пиши 65 . Вместо этого напишите 'A'

4. ciphertext является неинициализированным, как strncat и неопределенное поведение. Вам нужен нулевой терминатор, которого нет, когда он неинициализирован.

5. @chqrlie, да, я использую <cs50.h>. Спасибо вам за вашу помощь, ваши предложения по улучшению действительно помогли.

Ответ №1:

strncat Функция ожидает, что ее первым аргументом будет строка с нулевым завершением, к которой она добавляется. Вы вызываете его, ciphertext пока он неинициализирован. Это означает, что вы читаете неинициализированную память, возможно, читаете дальше конца массива, вызывая неопределенное поведение.

Вам нужно создать ciphertext пустую строку, прежде чем вызывать strncat ее. Кроме того, вам необходимо добавить 1 к размеру этого массива, чтобы учесть завершающий нулевой байт в завершенной строке, чтобы предотвратить списание ее конца.

 char ciphertext[strlen(plaintext) 1];
ciphertext[0] = 0;
 

Комментарии:

1. Дополнительный совет: plaintext необходимо быть свободным в конце программы. Это причина #define , по которой использование типов указателей не является хорошей идеей: оно затемняет указатель.

2. @Юн: Боюсь, вы ошибаетесь. get_string() действительно выделяет память, но пользователь не несет ответственности за освобождение этих выделенных объектов, поскольку библиотека регистрирует функцию atexit для выполнения очистки. cf cs50.readthedocs.io/libraries/cs50/c/#c.get_string

3. @Yun: эта библиотека действительно проблематична во многих отношениях. Он подходит только для начинающих, которым будет полезно не изучать тайные ловушки и подводные scanf() камни и другие функции ввода, но которые могут приобрести контрпродуктивные привычки. Самое главное, что они не узнают об указателях, что является самой сложной концепцией для усвоения в C.

4. @chqrlie, может быть, ты и прав! Я думал get_string , что это определяемая пользователем функция, которая просто не была дана для краткости. Опять же, ничто в вопросе не относится к этой сторонней библиотеке, насколько я могу судить.

5. @Yun: операция только что подтвердила использование <cs50.h> . Эта библиотека довольно популярна, и хотя она дает новичкам преимущество в написании небольших программ, решающих конкретные проблемы, некоторые из их идей кажутся контрпродуктивными, например, скрываться char * за typedef ( string ).

Ответ №2:

В коде есть несколько проблем:

  • вы не проверяете наличие и длину аргумента командной строки
  • массив должен быть выделен с 1 дополнительным байтом для нулевого терминатора и инициализирован как пустая строка для strncat() правильной работы.
  • вместо жесткого кодирования ASCII-значений , таких как 97 и 65 , используйте символьные константы, такие как 'a' и 'A'
  • strncat() это перебор для вашей цели. Вы могли бы просто написать ciphertext[i] = l; вместо strncat(ciphertext, amp;l, 1)
  • islower() и isupper() определяются только для положительных значений типа unsigned char и специального отрицательного значения EOF . Вы должны приводить char аргументы, (unsigned char)c чтобы избежать неопределенного поведения в байтах, отличных от ASCII, на платформах, где char используется подписанный тип.
  • избегайте избыточных тестов, таких как islower(xxx) != 0 . Более идиоматично просто писать if (islower(xxx))

Вот измененная версия:

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

int main(int argc, string argv[]) {
    // Testing the argument
    if (argc < 2 || strlen(argv[1]) != 26) {
        printf("invalid or missing argumentn");
        return 1;
    }
    // Putting all the argv letters into an array called key
    char key[26];
    memcpy(key, argv[1], 26);
    
    string plaintext = get_string("plaintext: ");
    int len = strlen(plaintext);
    
    // Define an array called ciphertext, the length of the inputted text, to hold ciphertext chars and a null terminator
    char ciphertext[len   1];
    
    // Looping through the inputted text, checking for upper and lower case letters
    for (int i = 0; i < len; i  ) {
        unsigned char c = plaintext[i];

        if (islower(c)) {        // The letter is lower case
            int index = c - 'a'; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            ciphertext[i] = tolower((unsigned char)key[index]);
        } else
        if (isupper(c)) {
            // The letter is uppercase
            int index = c - 'A'; // Converting from ascii to decimal value and getting it into alphabetical index (0-25)
            ciphertext[i] = toupper((unsigned char)key[index]);
        } else {
            // other characters are unchanged
            ciphertext[i] = c;
        }
    }
    ciphertext[len] = '';  // set the null terminator

    printf("ciphertext: %sn", ciphertext);
    return 0;
}