ASCII table in Brainfuck
Congratulations. This was pretty readable code, by brainfuck standards. I was able to follow it.

By initializing your max cell to 256, you've made your code portable to work on non-wrapping brainfuck interpreters as well.  If you just wanted it to work on a wrapping brainfuck interpreter (i.e., one that works modulo 256), you could have left the cell set to 0.  I considered changing the program to just loop 256 times instead of comparing the character against the maximum value, but it turns out that 255 times is much easier than looping 256 times when the machine operates modulo 256.  You already have a comparison routine, so it's easy to just use it again.  (Though preferably not by copying and pasting the code — more on that below.)

While there may be simpler ways to print a number as a decimal string, you asked to optimize for readability, so I have opted to stay close to your original strategy.

The key to readability, I think, is to add strategic comments.  In brainfuck, any characters other than the eight commands (&gt;&lt;+-.,[]) are ignored.  You can also add comments of the form [-][Any comment text goes here], with two caveats:


The pointer must be pointing to a cell containing 0 or a cell that you want to be zeroed.
Any character can appear inside, as long as square brackets occur in pairs.


The main goals of your comments should be to clarify:


What the intention of each chunk of code is.

It is not so important, in my opinion, to be able to follow every single micro-operation as long as you can understand the big picture.
What the memory layout is, and why it is laid out that way.

I've given "variable names" to all important cells.  There is also a long explanatory comment in the code below about what I call "frames", or groups of consecutive cells.
Which cell is the current one at any point in the program.

It's a relatively minor bug if a cell contains the wrong value.  However, if the pointer is not where it is supposed to be, the program blows up in a spectacular way.  Therefore, I have developed the notation to mark the current pointer location in parentheses at strategic points throughout the code.




You raise a valid concern about repeating the comparison routine three times in your code.  The way to handle that in brainfuck is to let the routine crawl along the cells, frame by frame, like a construction crew working on a section of highway.  Just put the appropriate signposts to let it know where to stop crawling.



[-][
    # See MEMORY_LAYOUT below for explanation of SETUP.
]
SETUP
    +++++ +++ [ -
        &gt; +++++ ++      (Colon)
        &gt; ++++          (Space)
        &gt; +             (Newline)

        &gt;&gt;&gt;&gt;&gt;&gt;
        ++++ [ -
            &gt; +++++ +++ (Loop_limit)
            &lt;
        ]

        &gt;&gt;&gt;&gt;&gt; &gt;
        &gt; +++++ ++      (C_limit)
        &gt; +++++ +       (c_ascii)
        &gt;&gt;&gt;&gt;
        &gt; +++++ ++      (X_limit)
        &gt; +++++ +       (x_ascii)
        &gt;&gt;&gt;&gt;
        &gt; +++++ ++      (I_limit)
        &gt; +++++ +       (i_ascii)

        &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;
    ]
    &gt; ++                (Colon)
    &gt;&gt; ++ &gt;             (all_done)
    &gt;&gt; + &gt;&gt;&gt;&gt;           (Loop_limit)
    &gt;&gt; + &gt;&gt;&gt;&gt; ++        (C_limit)
    &gt;&gt; + &gt;&gt;&gt;&gt; ++        (X_limit)
    &gt;&gt; + &gt;&gt;&gt;&gt; ++        (I_limit)

    &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;
END_SETUP

MEMORY_LAYOUT
[-][
    # Using Roman abbreviations i, x, and c for units, tens, and hundreds,
    # respectively.  Braces { } indicate a frame: a repeated consecutive group
    # of cells.  Cells named with Uppercase are constants.  At all times,
    # parentheses indicate the current cell; neighboring cells may be noted
    # as well in the code.

    # The main loop starts working from the last frame.  It compares the last
    # two cells in the frame, and performs a carry if applicable.  It then
    # proceeds to the preceding frame and repeats.  The first "frame" is
    # special, as noted by the first cell being 0.  The comparison routine
    # will not be performed on it.  However, when ascii reaches Loop_limit,
    # all_done will be incremented to 1, since it is located at the offset
    # within the frame where carrying would increment a value.

  { (0)  Colon=":"=58  Space=" "=32  Newline="\n"=10  not_all_done=0 all_done=0 }
  { Compare_frame_0=1  0 0 0  Loop_limit=256       ascii="\0"=0   }
  { Compare_frame_1=1  0 0 0  C_limit=":"=58       c_ascii="0"=48 }
  { Compare_frame_2=1  0 0 0  X_limit=":"          x_ascii="0"    }
  { Compare_frame_3=1  0 0 0  I_limit=":"          i_ascii="0"    }
]
END_MEMORY_LAYOUT

&gt;&gt;&gt;&gt;
+
WHILE (not_all_done) [ -
    # Print one line of output
    &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt; .                   (c_ascii)
    &gt;&gt;&gt;&gt;&gt; &gt; .                           (x_ascii)
    &gt;&gt;&gt;&gt;&gt; &gt; .                           (i_ascii)
    &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt; . (Colon)
    &gt; .                                 (Space)
    &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt; .                        (ascii)
    INCR (ascii)
    +
    &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt; .                         (Newline)

    &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt;&gt; &gt;&gt;&gt;&gt;&gt; &gt;
    INCR (i_ascii)
    +

    &lt;&lt;&lt;&lt;&lt;
    REPEAT (Compare_frame_?) [
        &gt;&gt;&gt;&gt;&gt;
        IFEQ eq_flag=0 ascii_save=0 ?_limit (?_ascii)
            [ -
                &lt; - &lt; + &gt;&gt;
            ] ascii_save=ascii; diff=limit=limit MINUS ascii; ascii=0

            &lt;&lt;&lt; + (eq_flag=1)
            &gt;&gt; [
                &lt;&lt; - &gt;&gt; [ - &gt; + &lt; ]
            ]    ifneq { eq_flag=0 ; ascii=diff }; (diff=0)
            # Restore diff
            &gt; [ - &lt; + &gt; ]
            # Restore limit and ascii
            &lt;&lt; (ascii_save) [ - &gt; + &gt; + &lt;&lt; ]
            &lt; (eq_flag) [
            THEN
                [-][
                    # Perform carrying.  If ascii == Loop_limit, then
                    # "carrying" will set the all_done flag.
                ]
                &lt;&lt;&lt; +
                &gt;&gt;&gt;&gt;&gt; &gt; ----- -----
                &lt;&lt;&lt;
            ]
        END_IFEQ (eq_flag=0) ascii_save=0 ?_limit ?_ascii

        &lt;&lt;&lt;&lt;&lt; &lt;&lt;&lt;
        (Compare_frame_next)
    ]

    &gt;&gt;&gt;&gt;&gt;
    BOOL_NEGATE_AND_CLEAR not_all_done=0 (all_done)
        &lt; +
        &gt; [ - &lt; - &gt; ]
        &lt; (not_all_done)
    END_BOOL_NEGATE_AND_CLEAR
]


It is possible to reduce the frame size by one.  However, you would probably have to reuse the same cell for not_all_done and all_done, which would hurt readability.