C语言凯撒密码程序输出"?"符号问题排查
CS50x P2凯撒密码程序问题分析与修复
我正在学习CS50x的Problem Set 2,尝试实现凯撒密码程序,但还未完成核心加密逻辑就遇到无法正常输出明文的问题,程序运行时输出"?"符号。我是数组新手,已查阅部分资料仍不清楚原因,虽知道可能把代码复杂化了,或许可直接使用用户输入的string,但仍想了解当前代码失效的原因及修复方法。
程序需求:仅接受单个正整数命令行参数,无参数、参数为负数、参数非数字、参数数量多于1个时,均需提示"Usage: ./caesar key"。
原代码
#include <cs50.h> #include <stdio.h> #include <ctype.h> #include <string.h> #include <stdlib.h> string cipher(string word, int key); int main(int argc, string argv[]) { // Checks whether the user inputted only 1 argument if (argc == 2) { // Convert argv to an int int key = atoi(argv[1]); string plainText = get_string("plaintext: "); // Use function to return the (soon to be encrypted) string string cipherText = cipher(plainText, key); // Print for how long the word is int n = strlen(plainText); for (int i = 0; i < n; i++) { // Print the char of the array based upon the iteration of the loop which runs for however long the word is printf("%c", cipherText[i]); } printf("\n"); // If input is not a positive integer then it will fail if (key < 1) { printf("Usage: ./caesar key\n"); } } else { // If user inputted too many or no inputs then it will fail printf("Usage: ./caesar key\n"); } return 0; } string cipher(string word, int key) { // Find the length of the word in order to set the size of the array // This is so that both strings, the word inputted and the word to return are the same to add to int n = strlen(word); string cipherText[n]; // Loop through the input word for (int i = 0; i < n; i++) { // If char[i] is a letter then copy that letter into ciphertext if (isalpha(word[i])) { cipherText[i] = &word[i]; } else { cipherText[i] = &word[i]; } } // Return the array which, for example the input word is nobody // Return array[n, o, b, o, d, y] return cipherText[0-n]; }
代码失效的核心原因
- 数组类型完全错误:CS50的
string本质是char*,所以string cipherText[n]定义的是一个指针数组,但你需要的是存储字符的数组来构建新字符串,类型完全不匹配。 - 字符赋值逻辑错误:
cipherText[i] = &word[i]是把原字符串中每个字符的地址赋值给指针数组元素,根本没有复制字符本身,完全达不到构建新字符串的目的。 - 野指针与越界访问:
- 函数内的局部数组
cipherText存在栈上,函数执行结束后会被系统回收,返回它的指针属于返回野指针,访问这种指针会触发未定义行为(输出?就是典型表现)。 return cipherText[0-n]中的0-n是负数,属于数组越界访问,进一步加剧了未定义行为。
- 函数内的局部数组
- 参数验证不完整:仅用
atoi转换后检查key < 1有漏洞——如果输入12a这种混合字符,atoi会返回12,程序会错误认为参数合法;只有纯数字的正整数才符合要求。
修复方法
1. 修正cipher函数的字符串构建逻辑
把指针数组改成动态分配的字符数组(避免局部数组销毁的问题),直接复制字符并添加字符串结束符:
string cipher(string word, int key) { int n = strlen(word); // 动态分配内存,+1是为了存储字符串结束符'\0' char *cipherText = malloc(n + 1); if (cipherText == NULL) { // 检查内存分配是否成功 return NULL; } for (int i = 0; i < n; i++) { // 先直接复制原字符,后续再添加加密逻辑即可 cipherText[i] = word[i]; } cipherText[n] = '\0'; // 必须添加结束符,否则无法被识别为合法字符串 return cipherText; }
2. 完善命令行参数验证
新增函数检查参数是否全为数字,调整验证顺序(先确保参数合法,再处理输入):
// 新增函数:检查字符串是否全为数字 bool is_all_digits(string s) { for (int i = 0; s[i] != '\0'; i++) { if (!isdigit(s[i])) { return false; } } return true; }
修改main函数的参数验证逻辑:
int main(int argc, string argv[]) { // 先检查参数数量和是否全为数字 if (argc != 2 || !is_all_digits(argv[1])) { printf("Usage: ./caesar key\n"); return 1; } int key = atoi(argv[1]); // 再检查是否为正整数 if (key < 1) { printf("Usage: ./caesar key\n"); return 1; } string plainText = get_string("plaintext: "); string cipherText = cipher(plainText, key); if (cipherText != NULL) { printf("ciphertext: %s\n", cipherText); free(cipherText); // 释放动态分配的内存,避免内存泄漏 } return 0; }
3. 简化输出逻辑
修复后的cipherText是合法字符串,直接用printf("%s", cipherText)打印即可,不需要循环逐个字符输出。
内容的提问来源于stack exchange,提问作者Angus Hay
相关产品推荐
相关产品推荐

