不确定为啥 toupper() 会切断 C 中的最后一个字母

Posted

技术标签:

【中文标题】不确定为啥 toupper() 会切断 C 中的最后一个字母【英文标题】:Unsure as to why toupper() is cutting off last letter in C不确定为什么 toupper() 会切断 C 中的最后一个字母 【发布时间】:2021-10-20 08:35:09 【问题描述】:

所以这个程序的目标基本上是在终端(通过argv[])中获取一个 26 个字母的“键”,并使用它的索引作为替换指南。因此,您在终端中输入了 2 个输入,一个在 argv[] 中,一个只是一个普通的 get_string() 输入。 argv[] 输入将如下所示:./s YTNSHKVEFXRBAUQZCLWDMIPGJO 其中s 是文件名。然后get_string() 输入将如下所示:plaintext: HELLO。 (输入为HELLO)。然后程序将执行的操作是遍历明文输入中的所有字母,并根据argv[] 键的索引替换其字母索引。例如,H 的字母索引为7(其中a = 0 和z = 25),因此我们查看键YTNSHKV(E)FXRBAUQZCLWDMIPGJO 中的第7 个索引,在本例中为E .它对输入中的每个字母执行此操作,我们最终将得到输出ciphertext: EHBBQ。这就是它在终端中的样子:

./s YTNSHKVEFXRBAUQZCLWDMIPGJO
plaintext:  HELLO
ciphertext: EHBBQ

但我的输出是EHBB,因为当我使用toupper() 时,它会出于某种原因切断最后一个字母。

另外,大小写取决于明文输入,如果明文输入为hello, worldargv[] 键为YTNSHKVEFXRBAUQZCLWDMIPGJO,则输出为jrssb, ybwsp,如果输入为@987654345 @ 使用相同的键,输出将是JrssB, ybwsp

我基本上解决了这个问题,我的程序根据通过命令行输入的密钥将给出的明文替换为正确的密文。现在,假设明文输入是HELLO,而密钥是vchprzgjntlskfbdqwaxeuymoi(全部小写),那么它应该返回HELLO而不是hello。这是因为我的程序将命令行键中的所有字母放入长度为 26 的数组中,然后我循环遍历所有明文字母并将其匹配 ascii 值(减去某个数字使其进入 0-25 索引范围)与键中的索引。所以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, &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, &u, 1); // Using strncat() to append the converted plaintext char to ciphertext
        
        // If its a space, comma, apostrophe, etc...
        else
        
            strncat(ciphertext, &plaintext[i], 1);
        
    
    
    // prints out ciphertext output
    printf("ciphertext: ");
    for (int i = 0; i < strlen(plaintext); i++)
    
        printf("%c", ciphertext[i]);
    
    printf("\n");
    printf("%c\n", ciphertext[1]);
    printf("%c\n", ciphertext[4]);
    //printf("%s\n", ciphertext);
    return 0;

【问题讨论】:

不确定我是否重复这个,但是“char ciphertext[strlen(plaintext)];”在我看来很可疑。我更希望看到“char ciphertext[strlen(plaintext)+1];”,带有 +1 以容纳终止字符串的 \0。 至少:char ciphertext[strlen(plaintext)] -> char ciphertext[strlen(plaintext) + 1] 不要写65。相反,写'A' ciphertext 未初始化,因此 strncat 是未定义的行为。您需要一个空终止符,它在未初始化时不存在。 @chqrlie,是的,我正在使用 。感谢您的帮助,您的改进建议真的很有帮助。 【参考方案1】:

strncat 函数期望它的第一个参数是一个以空结尾的字符串,并附加到该字符串。您在未初始化时使用ciphertext 调用它。这意味着您正在读取未初始化的内存,可能读取到数组末尾之后,触发undefined behavior。

在调用strncat 之前,您需要将ciphertext 设为空字符串。此外,您需要将此数组的大小加 1 以说明已完成字符串上的终止空字节,以防止将其末尾写掉。

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

【讨论】:

附加建议:plaintext 需要在程序结束时释放。这就是为什么#define-ing 指针类型不是一个好主意的原因:它掩盖了指针。 @Yun: 恐怕你弄错了。 get_string() 确实分配了内存,但释放这些分配的对象不是用户的责任,因为库注册了一个 atexit 函数来执行清理。 cfcs50.readthedocs.io/libraries/cs50/c/#c.get_string @Yun:这个库确实有很多问题。它只适合初学者无需学习scanf() 的奥术陷阱和陷阱以及其他输入功能,但可能会养成适得其反的习惯。最重要的是,他们不会学习指针,这是 C 语言中最难掌握的概念。 @chqrlie 也许你是对的!我认为get_string 是一个用户定义的函数,只是为了简洁而没有给出。再说一次,据我所知,问题中没有任何内容涉及这个第三方库。 @Yun:OP刚刚确认使用&lt;cs50.h&gt;。这个库非常受欢迎,虽然它为初学者编写解决具体问题的小程序提供了一个良好的开端,但他们的一些想法似乎适得其反,例如将 char * 隐藏在 typedef (string) 后面。【参考方案2】:

代码存在多个问题:

您没有测试命令行参数的存在和长度 应为数组分配 1 个额外字节用于空终止符,并将其初始化为空字符串,以便 strncat() 正常工作。 不要硬编码 ASCII 值,例如 9765,而是使用字符常量,例如 'a''A' strncat() 对于您的目的来说太过分了。你可以写ciphertext[i] = l; 而不是strncat(ciphertext, &amp;l, 1) islower()isupper() 仅针对 unsigned char 类型的正值和特殊的负值 EOF 定义。您应该将 char 参数转换为 (unsigned char)c 以避免在 char 恰好是有符号类型的平台上的非 ASCII 字节上出现未定义的行为。 避免冗余测试,例如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 argument\n");
        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] = '\0';  // set the null terminator

    printf("ciphertext: %s\n", ciphertext);
    return 0;

【讨论】:

以上是关于不确定为啥 toupper() 会切断 C 中的最后一个字母的主要内容,如果未能解决你的问题,请参考以下文章

为啥导航栏会切断我的 UI Builder 的一部分,但在测试时没有切断它?

为啥我不能将 ToUpper() 应用于 OwnerNode?

为啥我的 Access 报告在打印时被切断?

我不确定为啥会出现此错误。删除主键中的重复记录

在 C# 的 ToUpper 或 SQL 中的 Upper 函数的性能方面哪个更好

有人能帮解释一下下面的C语言代码吗?主要是指针