I would check your subscripting, because whenever I see 0 and <= in a loop, I'm thinking buffer overruns.
Valgrind thinks so as well.
Code:
$ g++ -g main.cpp
$ valgrind ./a.out
==11395== Memcheck, a memory error detector
==11395== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==11395== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==11395== Command: ./a.out
==11395==
5
==11395== Invalid write of size 1
==11395== at 0x4008E9: set_char_at(char*, int, int, int, char) (main.cpp:5)
==11395== by 0x400994: main (main.cpp:20)
==11395== Address 0x5ab60d9 is 0 bytes after a block of size 25 alloc'd
==11395== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11395== by 0x400928: main (main.cpp:12)
==11395==
==11395== Invalid write of size 1
==11395== at 0x4008E9: set_char_at(char*, int, int, int, char) (main.cpp:5)
==11395== by 0x4009B7: main (main.cpp:22)
==11395== Address 0x5ab60d9 is 0 bytes after a block of size 25 alloc'd
==11395== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11395== by 0x400928: main (main.cpp:12)
==11395==
==11395== Invalid read of size 1
==11395== at 0x4C30F74: strlen (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11395== by 0x4F49228: std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==11395== by 0x4009CE: main (main.cpp:25)
==11395== Address 0x5ab60d9 is 0 bytes after a block of size 25 alloc'd
==11395== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11395== by 0x400928: main (main.cpp:12)
==11395==
*
**
***
==11395== Invalid read of size 1
==11395== at 0x52375D2: _IO_default_xsputn (genops.c:455)
==11395== by 0x52351F6: _IO_file_xsputn@@GLIBC_2.2.5 (fileops.c:1352)
==11395== by 0x522A6FA: fwrite (iofwrite.c:39)
==11395== by 0x4F48EC5: std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==11395== by 0x4F49236: std::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==11395== by 0x4009CE: main (main.cpp:25)
==11395== Address 0x5ab60d9 is 0 bytes after a block of size 25 alloc'd
==11395== at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==11395== by 0x400928: main (main.cpp:12)
==11395==
****
==11395==
==11395== HEAP SUMMARY:
==11395== in use at exit: 72,729 bytes in 2 blocks
==11395== total heap usage: 4 allocs, 2 frees, 74,777 bytes allocated
==11395==
==11395== LEAK SUMMARY:
==11395== definitely lost: 25 bytes in 1 blocks
==11395== indirectly lost: 0 bytes in 0 blocks
==11395== possibly lost: 0 bytes in 0 blocks
==11395== still reachable: 72,704 bytes in 1 blocks
==11395== suppressed: 0 bytes in 0 blocks
==11395== Rerun with --leak-check=full to see details of leaked memory
==11395==
==11395== For counts of detected and suppressed errors, rerun with: -v
==11395== ERROR SUMMARY: 5 errors from 4 contexts (suppressed: 0 from 0)