1

I wrote a text cipher program. It seems to works on text strings a few characters long but does not work on a longer ones. It gets the input text by reading from a text file. On longer text strings, it still runs without crashing, but it doesn’t seem to work properly.

Below I have isolated the code that performs that text scrambling. In case it is useful, I am running this in a virtual machine running Ubuntu 19.04. When running the code, enter in auto when prompted. I removed the rest of code so it wasn't too long.

#include <iostream>
#include <string>
#include <sstream>
#include <random>
#include <cmath>
#include <cctype>
#include <chrono>
#include <fstream>
#include <new>


bool run_cypher(char (&a)[27],char (&b)[27],char (&c)[11],char (&aa)[27],char (&bb)[27],char (&cc)[11]) {
//lowercase cypher, uppercase cypher, number cypher, lowercase original sequence, uppercase original sequence, number original sequence
std::ifstream out_buffer("text.txt",std::ios::in);
std::ofstream file_buffer("text_out.txt",std::ios::out);
//out_buffer.open();
out_buffer.seekg(0,out_buffer.end);
std::cout << "size of text: " << out_buffer.tellg() << std::endl;//debug
const int size = out_buffer.tellg();
std::cout << "size: " << size << std::endl;//debug
out_buffer.seekg(0,out_buffer.beg);
char *out_array = new char[size + 1];
std::cout << "size of out array: " << sizeof(out_array) << std::endl;//debug
    for (int u = 0;u <= size;u = u + 1) {
    out_array[u] = 0;
    }
out_buffer.read(out_array,size);
out_buffer.close();
char original[size + 1];//debug
    for (int bn = 0;bn <= size;bn = bn + 1) {//debug
    original[bn] = out_array[bn];//debug
    }//debug
    for (int y = 0;y <= size - 1;y = y + 1) {
    std::cout << "- - - - - - - -" << std::endl;
    std::cout << "out_array[" << y << "]: " << out_array[y] << std::endl;//debug
    int match;
    int case_n; //0 = lowercase, 1 = uppercase
        if (isalpha(out_array[y])) {
            if (islower(out_array[y])) {
            //std::cout << "out_array[" << y << "]: " << out_array[y] << std::endl;//debug
            //int match;
                for (int ab = 0;ab <= size - 1;ab = ab + 1) {
                    if (out_array[y] == aa[ab]) {
                    match = ab;
                    case_n = 0;
                    std::cout << "matched letter: " << aa[match] << std::endl;//debug
                    std::cout << "letter index: " << match << std::endl;//debug
                    std::cout << "case_n: " << case_n << std::endl;//debug
                    }
                }
            }
            if (isupper(out_array[y])) {
                for (int cv = 0;cv <= size - 1;cv = cv + 1) {
                    if (out_array[y] == bb[cv]) {
                    case_n = 1;
                    match = cv;
                    std::cout << "matched letter: " << bb[match] << std::endl;//debug
                    std::cout << "letter index: " << match << std::endl;//debug
                    std::cout << "case_n: " << case_n << std::endl;//debug
                    }
                }
            }
            if (case_n == 0) {
            out_array[y] = a[match];
            std::cout << "replacement letter: " << a[match] << " | new character: " << out_array[y] << std::endl;//debug
            }
            if (case_n == 1) {
            std::cout << "replacement letter: " << b[match] << " | new character: " << out_array[y] << std::endl;//debug
            out_array[y] = b[match];
            }

        }
        if (isdigit(out_array[y])) {
            for (int o = 0;o <= size - 1;o = o + 1) {
                if (out_array[y] == cc[o]) {
                match = o;
                std::cout << "matched letter: " << cc[match] << std::endl;//debug
                std::cout << "letter index: " << match << std::endl;//debug
                }
            }
        out_array[y] = c[match];
        std::cout << "replacement number: " << c[match] << " | new character: " << out_array[y] << std::endl;//debug
        }
    std::cout << "- - - - - - - -" << std::endl;
    }
std::cout << "original text: " << "\n" << original << "\n" << std::endl;    
std::cout << "encrypted text: " << "\n" << out_array << std::endl;
delete[] out_array;
return 0;
}

int main() {
const int alpha_size = 27;
const int num_size = 11;
char l_a_set[] = "abcdefghijklmnopqrstuvwxyz";
char cap_a_set[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char n_a_set[] = "0123456789";
std::cout << "sizeof alpha_set: " << std::endl;//debug
char lower[alpha_size] = "mnbvcxzasdfghjklpoiuytrewq";
char upper[alpha_size] = "POIUYTREWQASDFGHJKLMNBVCXZ";
char num[num_size] = "9876543210"; 
int p_run; //control variable. 1 == running, 0 == not running
int b[alpha_size]; //array with values expressed as index numbers
std::string mode;
int m_set = 1;
    while (m_set == 1) {
    std::cout << "Enter 'auto' for automatic cypher generation." << std::endl;
    std::cout << "Enter 'manual' to manually enter in a cypher. " << std::endl;
    std::cin >> mode;
    std::cin.ignore(1);
    std::cin.clear();
        if (mode == "auto") {
        p_run = 2;
        m_set = 0;
        }
        if (mode == "manual") {
        p_run = 3;
        m_set = 0;
        }
    }
    if (p_run == 2) { //automatic mode
    std::cout <<"lower cypher: " << lower << "\n" << "upper cypher: " << upper << "\n" << "number cypher: " << num << std::endl;//debug
    run_cypher(lower,upper,num,l_a_set,cap_a_set,n_a_set);
    return 0;//debug
    }
    while (p_run == 3) {//manual mode
    return 0;//debug    
    }
return 0;
}

For example, using an array containing “mnbvcxzasdfghjklpoiuytrewq” as the cipher for lower case letters, I get “mnbv” if the input is “abcd”. This is correct.

If the input is “a long word”, I get “m gggz zzzv” as the output when it should be “m gkjz rkov”. Sort of correct but still wrong. If I use “this is a very very long sentence that will result in the program failing” as the input, I get "uas” as the output, which is completely wrong. The program still runs but it fails to function as intended. So as you can see, it does work, but not on any text strings that are remotely long. Is this a memory problem or did I make horrible mistake somewhere?

  • 8
    ***Is it possible to have memory problems that don’t crash a program?*** Yes, you could have corrupted the heap or other UB without crashing the program. – drescherjm Apr 30 '19 at 03:28
  • 2
    Memory problems are usually undefined behavior. Even if you don't get the program crashed, you should try your best to avoid them. – L. F. Apr 30 '19 at 03:28
  • 3
    Undefined behavior does not mean a program will crash. Sometimes if your very unlucky you get a result you expect when the program is still ill formed and has Undefined behavior. – drescherjm Apr 30 '19 at 03:29
  • 1
    A very, very brief look through this code reveals several possible execution paths that lead to undefined behavior, where an uninitialized variable gets used. Boom. Use your debugger to execute your program one line at a time, and observe its logical execution flow. This is what a debugger is for. – Sam Varshavchik Apr 30 '19 at 03:36
  • 1
    `char *out_array = new char[size + 1]; std::cout << "size of out array: " << sizeof(out_array) << std::endl;` This `sizeof(out_array)` does not do what you think it does. This will give you the `sizeof (char *)` which is more than likely 4 or 8. – PaulMcKenzie Apr 30 '19 at 03:39
  • If it doesn't work right you must have made a mistake. Don't blame the memory. – hookenz Apr 30 '19 at 05:04

1 Answers1

3

For your specific code, you should run it through a memory checking tool such as valgrind, or compile with an address sanitizer.

Here are some examples of memory problems that most likely won't crash your program:

  1. Forgetting to delete a small object, which is allocated only once in the program. A memory leak can remain undetected for decades, if it does not make the program run out of memory.
  2. Reading from allocated uninitialized memory. May still crash if the system allocates objects lazily at the first write.
  3. Writing out of bounds slightly after an object that sits on heap, whose size is sizeof(obj) % 8 != 0. This is so, since heap allocation is usually done in multiples of 8 or 16. You can read about it at answers of this SO question.
  4. Dereferencing a nullptr does not crash on some systems. For example AIX used to put zeros at and near address 0x0. Newer AIX might still do it.

    On many systems without memory management, address zero is either a regular memory address, or a memory mapped register. This memory can be accessed without crashing.

    On any system I have tried (POSIX based), it was possible to allocate valid memory at address zero through memory mapping. Doing so can even make writing through nullptr work without crashing.

This is only a partial list.

Note: these memory problems are undefined behavior. This means that even if the program does not crash in debug mode, the compiler might assume wrong things during optimization. If the compiler assumes wrong things, it might create an optimized code that crashes after optimization.

For example, most compilers will optimize this:

int a = *p; // implies that p != nullptr
if (p) 
   boom(p);

Into this:

int a = *p;
boom(p);

If a system allows dereferencing nullptr, then this code might crash after optimization. It will not crash due to the dereferencing, but because the optimization did something the programmer did not foresee.

Michael Veksler
  • 8,217
  • 1
  • 20
  • 33
  • Thank you. I ran valgrind and it turns out it's not a memory problem. The problem was an infinite loop I didn't notice. – gattica1701 Apr 30 '19 at 16:41