概要
CS50のC言語を使用した課題で、printf()
を使用して文字列の出力を行った際に、出力され文字列の末尾に予期しない文字が出力されてしまっていたので、その原因と対処方法を備忘録として残します。
※課題は以下のサイトのものです。
https://cs50.jp/
問題となった処理
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
void get_ciphertext(string substitutionPattern, string plaintext, string ciphertext);
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("Usage: ./substitution key\n");
return 0;
}
else if (strlen(argv[1]) != 26)
{
printf("Key must contain 26 characters.\n");
return 0;
}
string plaintext = get_string("plaintext: ");
char ciphertext[strlen(plaintext)];
get_ciphertext(argv[1], plaintext, ciphertext);
// -2はNULL終端文字を除いた文字数を取得したいため
if (strlen(ciphertext) -2 != strlen(plaintext))
{
printf("Usage: ./substitution key\n");
return 0;
}
printf("ciphertext: %s\n", ciphertext);
}
void get_ciphertext(string substitutionPattern, string plaintext, string ciphertext)
{
int upperACode = 65;
int lowerACode = 97;
int plaintextLen = strlen(plaintext);
for (int i = 0; i < plaintextLen; i++)
{
char plaintextChar = plaintext[i];
if (isalpha(plaintextChar))
{
int targetCharLocation = plaintextChar;
char convertedChar = '\0';
if (isupper(plaintextChar))
{
targetCharLocation -= upperACode;
convertedChar = toupper(substitutionPattern[targetCharLocation]);
}
else if (islower(plaintextChar))
{
targetCharLocation -= lowerACode;
convertedChar = tolower(substitutionPattern[targetCharLocation]);
}
ciphertext[i] = convertedChar;
}
else if (ispunct(plaintextChar) || isspace(plaintextChar))
{
ciphertext[i] = plaintextChar;
}
else
{
break;
}
}
}
上記の処理はユーザーが入力したキーに基づいて文字列を暗号化し出力する処理です。
出力結果
substitution/ $ ./substitution VCHPRZGJNTLSKFBDQWAXEUYMOI
plaintext: hello, world
ciphertext: jrssb, ybwsp�V
正しく処理が行われていれば、出力結果はciphertext: jrssb, ybwsp
となるはずですが、実際は末尾に余計な文字が出力されてしまっています。
原因
string plaintext = get_string("plaintext: ");
char ciphertext[strlen(plaintext)];
ここでciphertext
に対してNULL終端文字列分の領域を確保しておらず、この後呼び出すメソッドの最後で、NULL終端文字をciphertext
に追加していなかったために発生したバグでした。
C言語では、動的にメモリを確保するようなデータ型(配列)を使う際には、データの末尾にNULL終端文字(\0)を入れる必要があります。静的なデータ型であれば決まったメモリが割り当てられるため問題ないのですが、動的なデータ型の場合は終端を明示的に示してあげないとそのデータがどこまで続いているのかわからなくなってしまいます。
今回はNULL終端文字を入れていなかったため、文字列の終端がわからず、printf 関数でメモリ上の不定な領域を読み込んでしまい、予期しない文字(今回の場合は �V)が表示されてしまいました。
対策
ciphertext
の宣言時にNULL終端文字分の領域を確保してあげます。
char ciphertext[strlen(plaintext) + 1];
ループの最後で、NULL終端文字を追加します。
ciphertext[plaintextLen] = '\0';
修正後のソース
#include <cs50.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
void get_ciphertext(string substitutionPattern, string plaintext, string ciphertext);
int main(int argc, string argv[])
{
if (argc != 2)
{
printf("Usage: ./substitution key\n");
return 1;
}
string key = argv[1];
int keyLen = strlen(key);
if (keyLen != 26)
{
printf("Key must contain 26 characters.\n");
return 1;
}
for (int i = 0; i < keyLen; i++)
{
char keyChr = key[i];
int firstChrPointer = strchr(key, keyChr) - key;
int lastChrPointer = strrchr(key, keyChr) - key;
if (!isalpha(keyChr) || firstChrPointer != lastChrPointer)
{
printf("Usage: ./substitution key\n");
return 1;
}
}
string plaintext = get_string("plaintext: ");
char ciphertext[strlen(plaintext) + 1];
get_ciphertext(key, plaintext, ciphertext);
if (strlen(ciphertext) != strlen(plaintext))
{
printf("Usage: ./substitution key\n");
return 1;
}
printf("ciphertext: %s\n", ciphertext);
}
void get_ciphertext(string substitutionPattern, string plaintext, string ciphertext)
{
int upperACode = 65;
int lowerACode = 97;
int plaintextLen = strlen(plaintext);
for (int i = 0; i < plaintextLen; i++)
{
char plaintextChar = plaintext[i];
if (isalpha(plaintextChar))
{
int targetCharLocation = plaintextChar;
char convertedChar = '\0';
if (isupper(plaintextChar))
{
targetCharLocation -= upperACode;
convertedChar = toupper(substitutionPattern[targetCharLocation]);
}
else if (islower(plaintextChar))
{
targetCharLocation -= lowerACode;
convertedChar = tolower(substitutionPattern[targetCharLocation]);
}
ciphertext[i] = convertedChar;
}
else if (isblank(plaintextChar) || isdigit(plaintextChar) || ispunct(plaintextChar))
{
ciphertext[i] = plaintextChar;
}
else
{
break;
}
}
ciphertext[plaintextLen] = '\0';
}