【问题标题】:caesar ciphering (segmentation fault)凯撒密码(分段错误)
【发布时间】:2017-08-05 00:27:21
【问题描述】:

我正在尝试编写一个对用户提供的字符串进行 Caesar 加密的程序,但每次我尝试运行它时都会弹出错误“Segmentation Fault”。我做错了什么?

    int main(int argc, string argv[])
{
    if (argc != 2)
    {
        return 1;
    }

    int key = atoi(argv[1]);

    printf("Plaintext: ");
    string Ptext = get_string();
    string cipher = 0;

    if(Ptext != NULL)
    {
        for(int i = 0, n = strlen(Ptext); i < n; i++)
        {
            if(isalpha(i))
            {
                if(isupper(i))
                {
                    cipher += toupper(((i + key) % 26));
                }
                else
                {
                    cipher += tolower(((i + key) % 26));
                }
            }
            cipher += i;
        }
        printf("Ciphertext: %s", cipher);
        printf("\n");
    }
}

【问题讨论】:

  • 你的代码在我看来像 C++。 string 在 C 中不存在,字符串附加也不存在。什么是get_string()
  • 这也是您在 if 语句中测试 if i,索引值,而不是索引 i 处的字符的情况,例如Ptext[i]
  • @FelixGuo 这个cs50 课程确实提供了typedef char *string...
  • @AshleyMoe :我添加了标签cs50caesar-encryption,因为stringget_string() 的使用是常规的,除了在CS50 课程中。另请注意CS50 站点。
  • 如果是 C,这个程序只是一个巨大的未定义行为。您只需对指针进行一些算术运算,然后取消引用它,这样 SegFault 就不足为奇了——我什至会说这是预期的行为

标签: c segmentation-fault cs50 caesar-cipher


【解决方案1】:

根据我在 cmets 中阅读的内容,让我们解决您的帖子最初缺少的一些信息。有一个typedef char* string 以及一个提供的get_string() 函数。基于这些假设,您首先要分配足够的空间来存储生成的密码字符串。

C 中的字符串只是分配的字符数组,因此您需要以某种方式分配该内存。最好的方法是使用malloc 在堆上分配它。

更改这一行:string cipher = 0;string cipher = malloc(strlen(Ptext) + 1); 这将分配足够的空间来存储您生成的密文。现在,您将需要以某种方式跟踪索引。添加int size = 0; 现在,将循环更改为如下所示:

for(int i = 0, n = strlen(Ptext); i < n; i++)
    {
        if(isalpha(i))
        {
            if(isupper(i))
            {
                cipher[size++] = toupper(((i + key) % 26));
            }
            else
            {
                cipher[size++] = tolower(((i + key) % 26));
            }
        }
        else 
        {
            cipher[size++] = i;
        }
    }
    cipher[size] = 0; // for the null terminator

这和你认为+= 会做的事情是一样的。它将在分配的字符数组中的正确位置插入字符,然后将大小增加 1,以便下一次插入将在下一个位置。打印字符串后要做的最后一件事是调用free(cipher); 以释放基于malloc 分配的内存

我建议查看mallocfree 的工作原理,以及字符数组在C 中的工作原理。这是typedef char* string 的缺陷之一,也就是说,它抽象出了string真的是在 C 中,它是一个指向 char 数组的指针。

【讨论】:

  • 到目前为止答案很好!但是我有两点意见:“// for the null pointer”是错误的,这是NUL终止符(字符串的结束标记),它绝对不是指针。然后,使用数组通常比使用malloc() 更容易。
  • 关于 VLA,它们在 C99 中是强制性的,在 C11 中是可选的,因此您对此的担忧确实是相关的。但通常,使用静态大小的缓冲区就足够了。
  • 同意。我认为如果在课程中,学生还没有了解动态分配,那么静态缓冲区就可以了。在这种情况下,他们还可以访问所需的确切长度。
  • 不想让你紧张,但如果你真的想处理任何尺寸,不要使用int,而是使用size_t
  • 哇。是不是缺少else?:cipher[size++] = i; 应该只对编码的字符执行。我看到这是来自 OP 的复制面食,所以这是同样的逻辑错误。然后,循环内的每个i 都应该是Ptext[i] ...
【解决方案2】:

我编写了一个可用的 Ceasar-cypher-function,它与我得到的代码一样接近。

下面是在线测试功能的一个稍微改变的变体。我只测试了第二个变体,并且只使用了在线 c++ 编译器(链接如下),虽然它是 c 代码。

我希望你可以用它来推理程序的几个部分。

我试图解释的一点是密码字符的计算。

对于大写字符的计算是

'A'+((26 + c -'A' + (key%26)) % 26);

第二项是

(26 + c -'A' + (key%26)) % 26 // 这个结果是一个介于 0 和 25 之间的数字,

如果 key 仅介于 -25 和 25 之间,则术语 (key%26) 可能只是 key

如果 key 始终为正数或零,则不需要添加 26(即 (26 + )。

所以对于从 0..25 开始的键,术语将是

(c -'A' + key) % 26

c -'A' 将字符 c 从 0..25 转换为数字,然后添加键(=offset 或 shift),然后将所有 26 或更高的数字按术语 % 26 向下移动.

所以现在你有一个从 0 到 25 的数字,它已经代表了密码字符。 但你真的想要一个从“A”到“Z”的字符,而不是从 0..25 的数字。

您希望 0 为“A”,1 为“B”,2 -> 'C' ... 24->Y 和 25->'Z'。 所以你将'A'的ascii值添加到数字中。这就是术语'A' +(源代码中的第一个术语)的原因。这会产生所需的 Ceasar-cipher-char。

您可能已经注意到,string 类型有点令人困惑。 我用char* 替换了string。如果您提供类型 string 和函数 get_string 的声明,我们可以将其重新合并到代码中,但在我看来,字符串也不是 c 中类型的好名称。

如果您对代码有任何疑问,请询问。

您仍然可以对代码进行几处改进,就像几乎总是可以做的那样,例如

  • 将文字 26 放入 const 变量中
  • (key%26) 操作一次而不是多次
  • 使用cipher[..] 符号代替*pb
  • ...等

所以这是程序:

int main(int argc, string argv[])
{
    if (argc != 2)
    {
        return 1;
    }

    int key = atoi(argv[1]);

    const char * Ptext = get_string();

    if(Ptext != NULL)
    {
        char * cipher = 0;       
        size_t n = strlen(Ptext);

        printf("Plaintext: %s\n", Ptext);

        cipher = (char *) malloc((n+1)*sizeof(char));

        if (!cipher)
            return 1;

        char * pb = cipher;
        char c;

        for(int i = 0 ; i < n; i++)
        {
            c = Ptext[i];

            if(isalpha(c))
            {
                if(isupper(c))
                {
                    *pb = 'A'+((26 + c -'A' + (key%26)) % 26);
                }
                else
                {
                    *pb = 'a'+((26 + c -'a' + (key%26)) % 26); 
                }
            }
            else
            {
                *pb = c; 
            }
            ++pb;
        }

        *pb='\0';

        printf("Ciphertext: %s", cipher);
        printf("\n");

        free(cipher);
    }
    return 0;
}

几乎相同的代码稍作改动以用于在线测试

https://www.onlinegdb.com/online_c++_compiler

#include <iostream>
#include <cstring>

using namespace std;

const char hw[]  = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG";

const char * get_string()
{
    return hw;
}

int main()
{
    int key = atoi("-3");

    const char * Ptext = get_string();

    if(Ptext != NULL)
    {
        char * cipher = 0;       
        size_t n = strlen(Ptext);

        printf("Plaintext: %s\n", Ptext);

        cipher = (char *) malloc((n+1)*sizeof(char));

        if (!cipher)
            return 1;

        char * pb = cipher;
        char c;

        for(int i = 0 ; i < n; i++)
        {
            c = Ptext[i];

            if(isalpha(c))
            {
                if(isupper(c))
                {
                    *pb = 'A'+((26 + c - 'A' + (key%26)) % 26);
                }
                else
                {
                    *pb = 'a'+((26 + c -'a' + (key%26)) % 26); 
                }
            }
            else
            {
                *pb = c; 
            }
            ++pb;
        }

        *pb='\0';

        printf("Ciphertext: %s", cipher);
        printf("\n");

        free(cipher);
    }
    return 0;
}

结果与https://en.wikipedia.org/wiki/Caesar_cipher显示的结果相同

【讨论】: