0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語で文字列を出力した際に、余計な値が出てしまう原因

Last updated at Posted at 2024-09-29

概要

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';
}

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?