0

screenshot

screenshot

I'm having a problem with an output. Sometimes output adds an extra characters, different each time the code runs. For example, when I use key "1" and write "a", it displays "bç" or "bv" or anything else weird.

I know that the problem may be fixed by adding "+1" in a loop here for (int i = 0; i < text_length; i++). Also I know that the problem is because at the end of the string is '\0' character.

But the things is that plaintext length = ciphertext length. So even in the loop there should be NO problems.

So I don't understand why it's fixed by adding +1 in a loop and I don't understand why it needs to add +1.

Please, explain this to me.

debug50 and variable tracking hasn't helped.

Here's what I found so far on this issue: Pset2 Caesar: extra letter printed at the end of ciphertext

#include <stdio.h>
#include <cs50.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>

int main(int argc, string argv[])
{
    // check if there's only one argument provided
    if (argc != 2)
    {
        printf("Usage: ./caesar key\n");
        return 1;
    }

    // check if argument is digit
    string string_key = argv[1];
    for (int i = 0, n = strlen(string_key); i < n; i++)
    {
        if (isdigit(string_key[i]) == false)
        {
            printf("Usage: ./caesar key\n");
            return 1;
        }
    }

    // convert string_key to int
    int key = atoi(string_key);

    // prompt user for a string to encipher
    string plaintext;
    do
    {
        plaintext = get_string("plaintext: ");
    }
    while (plaintext[0] == '\0');


    int UPPERCASE_DIFFERENCE = 65;
    int LOWERCASE_DIFFERENCE = 97;

    // get length of plaintext
    int text_length = strlen(plaintext);

    // create ciphertext array with length of plaintext
    char ciphertext[text_length];

    // loop through plaintext
    char character;
    for (int i = 0; i < text_length; i++)
    {
        character = plaintext[i];
        // check if character is a letter
        if (isalpha(character))
        {
            // check if character is an uppercase
            if (isupper(character))
            {
                // convert to alphabetical numbers, shift to key, convert back to ascii
                character = character - UPPERCASE_DIFFERENCE;
                character = (character + key) % 26;
                character = character + UPPERCASE_DIFFERENCE;
                // add to ciphertext
                ciphertext[i] = character;
            }
            else
            {
                // convert to alphabetical numbers, shift to key, convert back to ascii
                character = character - LOWERCASE_DIFFERENCE;
                character = (character + key) % 26;
                character = character + LOWERCASE_DIFFERENCE;
                // add to ciphertext
                ciphertext[i] = character;
            }
        }
        else
        {
            ciphertext[i] = character;
        }
    }
    // print ciphertext

    printf("ciphertext: %s\n", ciphertext);
}
4
  • I understood! I've added this ciphertext[i + 1] = '\0'; at the end of a loop. I think that the error was because I didn't have an explicit ending '\0' of the char ciphertext array. But why didn't it work with length in a loop? Also how length + 1 fixes the issue?
    – D3sl0nG3r
    Commented Jan 28, 2021 at 13:15
  • I think I got it again. In order to print a string, it should be one char bigger than the plaintext's length. Because strings have '\0' at the end and count as one character. But why the code in the description works for all cases instead of "a" (first case) and world, hello! (last case)?
    – D3sl0nG3r
    Commented Jan 28, 2021 at 13:33
  • Also why and how does the code work if we add + 1 to the length of the text? Let's take "abc" as example. When i becomes 3, i < 4 (length) = true, how do we know the end of string? And what get's added to the string if there's nothing to add?
    – D3sl0nG3r
    Commented Jan 28, 2021 at 13:44
  • I get it. When there's nothing to add — plaintext[i] becomes 0. When 0 get's added to the ciphertext — it's the same as '\0'. But still, why the code in the description works for all cases instead of "a" (first case) and "world, hello!" (last case)?
    – D3sl0nG3r
    Commented Jan 28, 2021 at 13:48

2 Answers 2

2

You have to understand how printf and strlen work. Both of them (and several other functions) depend on the existence of the end of string marker, \0 at the end of the target string. If the EOS marker isn't there, they will keep reading data from memory until they find some random data byte that looks like the EOS marker, or 0x00.

Next, you need to understand that strlen() returns the number of chars in a string, excluding the EOS marker. BUT, when declared, a string needs to have 1 added to a strlen() result to allow space for the EOS marker. Your code doesn't allow for this when ciphertext is declared.

More importantly, during processing, the code inserts characters into ciphertext one at a time, BUT IT DOESN'T ADD AN EOS MARKER AT THE END! Even if it did, there's no room for it. We'll come back to the impact of that.

So, because there's no EOS marker at the end of ciphertext, the printf statement will read whatever physical data follows in memory because it thinks it's part of the string, treating it as ASCII codes, until it hits a 0x00 byte. That's why it prints random printable and unprintable characters at the end.

Now, there's another issue or two here. First, let's consider this code:

// prompt user for a string to encipher
    string plaintext;
    do
    {
        plaintext = get_string("plaintext: ");

This is going to cause problems. You have to understand what happens when a string (or later, a char array) is declared and how space for it is allocated. When a string is declared, the space for the string has to be allocated somehow. Once declared, a string is immutable. That means that the space allocated for it cannot be changed. It doesn't matter if you're using it for something shorter, but anything longer can possibly lead to data corruption.

So how does that happen here? Consider the declaration, string plaintext;. This declares a string of length 0. It doesn't initialize the string at the same time, so no space is allocated to it. But, C will allow data to be assigned to it later anyways. The problem with that is that C will overwrite the physical memory that follows with whatever the code says to write to plaintext. So, in this code, if the code reads a 20 char string with getstring(), it would overwrite the next 20 bytes in memory, whatever they're being used for, corrupting that data.

The code needs to both declare plaintext and initialize it with getstring in the same statement.

Don't worry about checking that something is written into it. That's handled inside getstring. Also, if your code encodes correctly, it'll handle no data correctly too. (It's too early in the course to worry about that all the time anyways.)

The declaration of ciphertext has a similar problem. ciphertext doesn't allow space for the EOS marker. Because of this, if the code tries to write the EOS marker, it'll corrupt the first byte following the string. That's why the +1 char when ciphertext is allocated.

Any questions? ;-)

If this answers your question, please click on the check mark to accept. Let's keep up on forum maintenance. ;-) \0

6
  • Thank you for this explanation! I understood about EOS marker, but I don't quite get the declaration and initialization part. Let's take your explanation for example: in this code, if the code reads a 20 char string with getstring(), it would overwrite the next 20 bytes in memory, whatever they're being used for, corrupting that data. Why C can't just push those 20 bytes somewhere to the right (metaphorically saying) and allow to allocate as much space as we want?
    – D3sl0nG3r
    Commented Jan 29, 2021 at 8:08
  • Because C simply wasn't designed that way. Remember, strings are immutable. Once a string (aka, a char array) is created, it's size cannot be altered. Other languages will do this (for example, python), but not C.
    – Cliff B
    Commented Jan 29, 2021 at 8:16
  • I understand the following: when we declare a string — we are just telling C that — this thing exists, but has nothing in it yet, so don't allocate any memory for it. After we assign data to string — c allocates memory to it. So instead of pushing 20 bytes to the right, we're just overwriting those bytes (they had some data in it.) with our string? I understand that it's a data corruption? But how big is this issue? Can they make something go horribly wrong? Where do those bytes go then? And which data did those bytes hold before we overwrote them?
    – D3sl0nG3r
    Commented Jan 29, 2021 at 8:20
  • Also if we don't allocate the memory yet after declaring a string, why those 20 bytes should be overwritten when we assign data later?
    – D3sl0nG3r
    Commented Jan 29, 2021 at 8:52
  • Thank you so much, Cliff! My friend developer has explained it to me too. All your memory is like a long array. For example, you declared two strings — one of them has strlen of 0. When you assign data to that first string and if the data you assign is more than 0 characters — you will overwrite the part of 2nd string (if those two strings are adjacent). Length of overwritten space will equal the length of assigned data of 1st string. A lot of errors in C are because C handles memory on a low-level.
    – D3sl0nG3r
    Commented Jan 29, 2021 at 9:36
0

My friend developer has explained it to me too.

All your memory is like a long array. For example, you declared two strings — one of them has strlen of 0. When you assign data to that first string and if the data you assign is more than 0 characters — you will overwrite the part of 2nd string (if those two strings are adjacent). Length of overwritten space will equal the length of assigned data of 1st string.

A lot of errors in C are because C handles memory on a low-level.

3
  • Exactly. But, let's clarify something. When a string (a char array) is allocated, the memory is indeed allocated. If a string of length 10 is declared, 10 bytes are allocated in stack memory for that string variable. Memory isn't allocated when chars are later assigned, the memory is allocated when the var is declared.
    – Cliff B
    Commented Jan 29, 2021 at 10:47
  • Next, let's be clear about how the overwrite happens. Say you have a string of length 8 chars in stack memory, and say that it's followed by an int in stack memory. Now, say that a 10 char string, plus the EOS marker is written to the string var. In reality, the first 8 bytes of the string will be written to the string var, but the remaining 2 bytes plus the EOS marker, will overwrite three bytes of the int variable. Is it a big deal? That depends. Does the program keep baseball scores? Does it record election votes? Or does it operate launch controls in a nuclear missile silo???? ;-)
    – Cliff B
    Commented Jan 29, 2021 at 10:51
  • Thank you so much, Cliff!
    – D3sl0nG3r
    Commented Jan 30, 2021 at 12:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .