-1

Code:

#include <iostream>

char* intToCharArray(int val)
{
    int len=0;
    bool neg = false;

    if (val < 0)
    {
        val = -val;
        len = ((int)log10(val)) + 2;
        neg = true;
    }
    else
    {
        len = ((int)log10(val)) + 1;
    }

    char* cons = new char[len+1];

    if (neg)
    {
        cons[0] = '-';
        for (int i = 1; i <= len-1; i++)
        {
            cons[len-i] = (val % 10) + '0';
            val /= 10;
        }
    }
    else
    {
        for (int i = 0; i <= len; i++)
        {
            cons[len-i-1] = (val % 10) + '0';
            val /= 10;
        }
    }

    cons[len] = '\0';

    return cons;
}

int main(int arc, char* arv)
{
    int num = 444;
    char* arr = intToCharArray(num);
    int count = 0;
    std::string test = std::string(arr);
    std::string beg = "test";

    std::cout << test << std::endl;

    //changing it like this works
    arr = intToCharArray(num * 6);
    test = std::string(arr);

    std::cout << test << std::endl;

    //but changing it with a loop crashes the program
    for (int i = 0; i < 5; i++)
    {
        arr = intToCharArray(num * i);
        test = std::string(arr);

        std::cout << test << std::endl;
    }

    return 0;
};

Basically, I'm trying to convert an integer into a string. Everything seems to work... unless I run the code in a loop. I have absolutely NO idea what could even possibly be the problem. I've never run into, or even heard of, a problem like this. I'm honestly not even sure what to ask, so Google has been no help either.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    What is the problem with the loop? Incorrect output? Crash? – Retired Ninja Apr 01 '21 at 20:41
  • 2
    *Basically I'm trying to convert an integer into a string.* -- `int i = 10; std::string s = std::to_string(i);` – PaulMcKenzie Apr 01 '21 at 20:42
  • I get a popup from Visual Studio saying "abort() has been called" – Elizabeth Enns Apr 01 '21 at 20:43
  • 1
    @ElizabethEnns Why are you not simply returning `std::string` if the goal is to convert to a string? Why the need for `char *` and dynamic memory allocation? Anyway, as pointed out in the previous comment `std::to_string()` does all of this work already. – PaulMcKenzie Apr 01 '21 at 20:46
  • I know I can convert it another way. I'm now more concerned with what's going on here than actually converting ints to string – Elizabeth Enns Apr 01 '21 at 20:49
  • 1
    The first loop iteration i = 0, which means your value is 0 and that is not handled properly, and that results in bad_alloc. I suspect log10(0) behaves badly, I added `std::cout << "Allocating " << (len + 1) << " bytes" << std::endl;` before the allocation and I get `Allocating -2147483646 bytes` the 3rd time its called (which would be the first loop iteration with i = 0) – Borgleader Apr 01 '21 at 20:49
  • 1
    What's `log10(0)` equal? – Woodford Apr 01 '21 at 20:51
  • OH! Oh wow, thank you, I totally would have missed than for DAAAAAAYS – Elizabeth Enns Apr 01 '21 at 20:51
  • @Woodford I honestly don't know, I've never really understood what a log actually /is/ – Elizabeth Enns Apr 01 '21 at 20:52
  • 1
    Didn't the debugger give you an option to debug at the abort? – drescherjm Apr 01 '21 at 20:52
  • 1
    As a note, Wikipedia is really great at explaining [just about anything](https://en.wikipedia.org/wiki/Logarithm). – tadman Apr 01 '21 at 20:53
  • 1
    The last `for` loop (executed when `neg` is `true`) runs `i` from `0` to `len` inclusive. The last iteration modifies `cons[-1]` which does not exist. That causes undefined behaviour. There may be other instances of undefined behaviour. Running a function with potential for undefined behaviour in a loop increases likelihood of seeing symptoms of that undefined behaviour. – Peter Apr 01 '21 at 20:53
  • 1
    Related to log10(0): [https://stackoverflow.com/questions/38329176/how-to-set-returned-value-of-log100/3832935](https://stackoverflow.com/questions/38329176/how-to-set-returned-value-of-log100/38329355) – drescherjm Apr 01 '21 at 20:53
  • @drescherjm No, it said "press Retry to run with debugger" or something, but when I hit retry nothing happened. I'm self taught, so I have no idea how to use the debugger...or what it even really is. I'm bad at asking questions as you may notice from the title, so I find it extremely hard to break in to new topics since I don't know enough about them to ask questions – Elizabeth Enns Apr 01 '21 at 20:54
  • 1
    ***press Retry to run with debugger"*** It should stop the program on the line in the debugger probably inside some standard library function implementation. At that point you change the "Stack Frame" combo box on the debugging toolbar to your code and many times it will give you an idea what is happening after you inspect your variables and position of the crash. – drescherjm Apr 01 '21 at 20:56
  • 1
    @ElizabethEnns -- I suggest you learn C++ by reading good books, and not freestyle and winging it. Even if you got your loop to work, there are so many flaws and fundamental issues with your code. You shouldn't learn C++ by starting out on the wrong foot. – PaulMcKenzie Apr 01 '21 at 20:56
  • Could you be more specific with what "flaws and fundamental issues" you're seeing? – Elizabeth Enns Apr 01 '21 at 20:59
  • 1
    @ElizabethEnns Who cleans up the memory you allocated? Your loop creates a memory leak. And the basic fundamental -- not using the C++ standard library. – PaulMcKenzie Apr 01 '21 at 21:00
  • 1
    I cant speak for Paul but the one I see is that you're leaking the dynamically allocated memory from `intToCharArray` – Borgleader Apr 01 '21 at 21:00
  • 1
    @PaulMcKenzie to be fair, sometimes not using the std library is the whole point of the exercise. ofc if i was building something i would use it but sometimes the point is to learn something and reinvent the wheel for the sake of learning how said wheel works. – Borgleader Apr 01 '21 at 21:01
  • How am I leaking memory? I'm using pointers, shouldn't it be rewritting the same space over and over? – Elizabeth Enns Apr 01 '21 at 21:02
  • @Borgleader couldn't have put it better myself – Elizabeth Enns Apr 01 '21 at 21:03
  • 1
    You do `new` but where is your `delete`. That's a memory leak. – Devolus Apr 01 '21 at 21:03
  • 1
    *How am I leaking memory?* -- You are issuing calls to `new[]`. They don't come for free. You need to call `delete[]`, else it is a memory leak. That's why I mentioned that you should return a `std::string` if that was your goal, and not get into the weeds of `new[]`. – PaulMcKenzie Apr 01 '21 at 21:04
  • Okay, I'm used to the GC just cleaning that up (Java background), how do I do what you're suggesting? – Elizabeth Enns Apr 01 '21 at 21:05
  • 1
    Use a container like std::vector/std::string, it owns its memory and will clean it up. – Borgleader Apr 01 '21 at 21:05
  • 1
    C++ is not Java. Pretend that Java does not exist when you are learning C++. – PaulMcKenzie Apr 01 '21 at 21:06
  • @PaulMcKenzie I tried returning a string, but I just could NOT get it to work – Elizabeth Enns Apr 01 '21 at 21:06
  • 1
    You should have posted the `std::string` attempt instead of a version that will have memory-leak issues. – PaulMcKenzie Apr 01 '21 at 21:07
  • The string version wouldn't even compile. I also didn't know I had a memory issue before posting here. Do you have any suggestions on how to implement this delete[] you've mentioned? – Elizabeth Enns Apr 01 '21 at 21:11
  • 1
    @ElizabethEnns [See this](http://coliru.stacked-crooked.com/a/00f02454c35f2945). Nice and simple. Also, `delete[]` is not something you "implement". Since you decided to take on the role of using dynamically allocated memory, you are solely responsible for issuing the calls to `delete[]` properly and appropriately. As a matter of fact, modern C++ programs in this day and age are considered "wrong" if there are any calls to `new[]` anywhere. If you're creating your own custom data structure or similar would you resort to using `new[]`. Simple user-programs shouldn't need to use it. – PaulMcKenzie Apr 01 '21 at 21:18
  • 1
    *how in the heck are you supposed to get anything done in c++ without using new?* Container classes such as `std::vector`, `std::deque`, `std::map`, `std::set`, `std::string`, etc. Smart pointers such as `std::unique_ptr`, `std::shared_ptr`, etc. Usage of those practically all but eliminates raw usage of `new[]`. – PaulMcKenzie Apr 01 '21 at 21:22
  • @PaulMcKenzie, `new` and `delete` should be avoided as much as possible, I agree, but a C++ program is not 'wrong' just because of using it. – Devolus Apr 01 '21 at 21:23
  • 1
    That's why I put "wrong" in quotes. In the real world of code reviews, C++ code with usage of `new[]` are now rejected by the reviewer. That has been superseded by `std::vector` – PaulMcKenzie Apr 01 '21 at 21:24
  • @PaulMcKenzie The entire reason I'm learning c++ is to use pointers. You're saying that using pointers in c++ is wrong? Because I haven't figured out a way to use a pointer without using new – Elizabeth Enns Apr 01 '21 at 21:25
  • 1
    Use `std::unique_ptr` and `std::shared_ptr`. Raw usage of `new` is discouraged in modern C++ programs. This is not just me saying this, many experts in the field, even the author of C++, Bjarne Stroustrup, says the same thing. – PaulMcKenzie Apr 01 '21 at 21:30
  • 1
    For learning reasons, it's good to understand how pointers work, but for real applicaitons you should definitely use smart pointers, which also help to avoid leaks like the one in your code. – Devolus Apr 01 '21 at 21:31
  • @PaulMcKenzie I'm sorry if I'm coming off in any way aggressive. I tend to rub people that way when I'm actually just really interested in something. I'm not questioning what you're saying is right, I'm asking questions because this is new information to me and I'm trying to understand. Again, apologies if I've offended. Thank you again for your help – Elizabeth Enns Apr 01 '21 at 21:32

2 Answers2

1

Ok theres to be 3 bugs in this code, let's tackle the immediate cause of the crash, the mishandled 0.

log10(0) outputs a giant negative number which then causes the new char[] to throw bad_alloc. To fix this you can change the line in the else clause (of if (val < 0)) to:

len = val == 0 ? 1 : ((int)log10(val)) + 1;

Another more sneaky problem which doesn't cause a crash on your machine (or mine, or the online compiler I used) is writing out of bounds in the array

for (int i = 0; i <= len; i++)

This stopping condition for this loop should be i < len otherwise you get an extra iteration where val is equal to 0 and index is equal to -1 which writes outside of the bounds of the array you allocated (which is UB and may manifest in a number of different ways).

The negative loop while not buggy can be simplified, i <= len - 1 is the same as the more conventional i < len.

Another bug is the memory leak. Each call to intToCharArray new's an array which is never de-allocated anywhere. You could fix this by calling delete arr; after creating a std::string from it, but this is error prone. It would be simpler to create a string in intToCharArray instead.

This is the 'final version':

#include <iostream>
#include <cmath>
#include <string>

std::string intToCharArray(int val)
{
    int len = 0;
    bool neg = false;

    if (val < 0)
    {
        val = -val;
        len = ((int)log10(val)) + 2;
        neg = true;
    }
    else
    {
        len = val == 0 ? 1 : ((int)log10(val)) + 1;
    }
    
    std::string cons; cons.resize(len + 1);

    if (neg)
    {
        cons[0] = '-';
        for (int i = 1; i < len; i++)
        {
            //std::cout << "Writing digit " << char((val % 10) + '0') << " at index " << (len - i) << '\n';
            cons[len - i] = (val % 10) + '0';
            val /= 10;
        }
    }
    else
    {
        for (int i = 0; i < len; i++)
        {
            //std::cout << "Writing digit " << char((val % 10) + '0') << " at index " << (len - i - 1) << '\n';
            cons[len - i - 1] = (val % 10) + '0';
            val /= 10;
        }
    }

    cons[len] = '\0';

    return cons;
}

int main(int arc, char** arv)
{
    int num = 444;
    
    for (int i = 0; i < 5; i++)
    {
        std::string test = intToCharArray(num * i);
        std::cout << test << std::endl;
    }

    for (int i = -1; i > -5; i--)
    {
        std::string test = intToCharArray(num * i);
        std::cout << test << std::endl;
    }

    return 0;
}

With this code I get the following output:

0
444
888
1332
1776
-444
-888
-1332
-1776
Borgleader
  • 15,826
  • 5
  • 46
  • 62
0

You are writing out of bounds here.

Lets assume len is two. Then you allocate 3 bytes.

char* cons = new char[len+1];
for (int i = 0; i <= len; i++)
{
    cons[len-i-1] = (val % 10) + '0';
    val /= 10;
}

Now the loop is:

len(2)-i(0)-1 = cons[1]
len(2)-i(1)-1 = cons[0]
len(2)-i(2)-1 = cons[-1]

It should be:

for (int i = 0; i < len; i++)
Devolus
  • 21,661
  • 13
  • 66
  • 113
  • Actually, no. If I do that the output I get is: ``` ═44 ═266 ═ ═44 ═88 ═133 ═177 ``` – Elizabeth Enns Apr 01 '21 at 20:57
  • You are wrong. It is plain to see and I detailed it for you to see, but when I run it in the debugger, this is exactly what happens. It writes out of bounds and I get an exception because of it (Visual Studio 2019). You can try any other compiler and you will most likely see undefined bheavior yourself. – Devolus Apr 01 '21 at 21:08
  • The problem seems to be with log(0) not being 1, which is what I assumed. log(0) is some other value, so I had to code around it – Elizabeth Enns Apr 01 '21 at 21:09
  • This is another problem of your code. ;) Beside this one. – Devolus Apr 01 '21 at 21:10
  • I literally copied and pasted my output after making the change you suggested, but sure, I'm wrong lol – Elizabeth Enns Apr 01 '21 at 21:10
  • You know what [undefined behavior](https://en.cppreference.com/w/cpp/language/ub) is? https://stackoverflow.com/questions/367633/what-are-all-the-common-undefined-behaviours-that-a-c-programmer-should-know-a – Devolus Apr 01 '21 at 21:13
  • for (int i = 0; i <= len-1; i++) { cons[(len-1)-i] = (val % 10) + '0'; val /= 10; } – Elizabeth Enns Apr 01 '21 at 21:18
  • If you do `len-1` then it's right again, but this is not what you posted in your code above in the `else` branch. – Devolus Apr 01 '21 at 21:25
  • I'm afraid @Devolus is correct, you are writing something at index -1 (which is undefined behavior) but it's a digit you didn't want (an extra 0) so the code appears to work. I added `std::cout << "Writing digit " << char((val % 10) + '0') << " at index " << (len - i - 1) << '\n';` inside the loop and for the first call I get 3 lines (digit 4 at index 2,1,0) but I get a 4th line digit 0 at index -1. Same ish output for 2664 theres a fifth line with 0 at index -1. – Borgleader Apr 01 '21 at 21:27
  • The output is correct, because the memory with the output is not affected. But because of `undefined bahvior` this doesn't mean much anyway. – Devolus Apr 01 '21 at 21:28
  • Yeah, just delete your comment to make me look worse, thanks. Yes, I'm aware that my initial code had problems, but the suggested correction also does not work. The loop I posted as a comment is the corrected version – Elizabeth Enns Apr 01 '21 at 21:35
  • I deleted my comment, because I missed that `-1`part in your comment, so my comment was wrong in that regard. The bug in your code is still there though. In your `if(neg)` branch you have the first branch with `len-1` which is correct, and in the `else` branch you have it not, which will cause the `undefined behavior` I described above. And if you applied my change it will certainly not cause wrong output, because I debugged it as well, so I know for sure even though I knew it before that. – Devolus Apr 01 '21 at 21:37