I’m not sure why you’re anchoring your pattern to the beginning of your string. Now, if you were capturing the part at the beginning of your pattern that would make sense, especially since this is preg_replace we’re talking about and anything not captured is going to be unavailable to replace with. (it will be available in $0, but as the entire string which sends you back to where you were before the match making it pointless)
Don’t anchor this pattern. Start and end your pattern with <code> and </code>. Before you make this search, the contents between your <code/> wrapper should have any html entities replaced with their htmlentity counterparts. ( < with <, > with > ) This will prevent ungreedy matches (matches that will end on the first occurrence of the marker instead of the last) from being closed if there’s a </code> inside your code. For instance if you were to post code showing someone how to post code.
If you leave the <code/> portions outside of your sub-match parenthesis, you can add them back manually and be left with only the contents of that wrapper inside the $1 back reference.
The i flag is fine, though if you’re positive your <code/> elements will all be lowercase, as is common when modern coding standards are used, you can leave that i flag off so the engine doesn’t have to test twice for each letter. (one for upper and once for lower, case)
You’ll probably want to include the s flag in this case though. If there’s no chance of a newline in your code blocks you can leave it out, but if there’s newlines in your code you need the s flag in order to tell the engine that it can include newlines in the dot (anything) metacharacters list of things to match.
In the replacement, you don’t want to include the <code/> portions inside your call to highlight_string. They’re not actually part of the code and shouldn’t be processed as if they were.
With all of that out of the way, you get this.
$html = preg_replace('#<code>(.+?)</code>#s',
'<code>' . highlight_string('$1') . '</code>',
$str
);
I noticed another pattern use the following bit, which is wasteful. Use the one-or-more (+) metacharacter instead of the zero-or-more metacharacter. If there’s nothing inside the <code/> wrapper, we want to ignore that wrapper. Otherwise it’s like opening an empty glass jug to see if there’s anything to drink inside of it. 
(.*?)
I noticed the use of a g flag out there somewhere as well. My best guess is that this was an artifact from someone doing Javascript development lately. Javascript regular expressions have a g flag to tell the engine that things should be replaced globally in the string, instead of just replacing the first found occurance. PHP doesn’t have a g flag though. At least not a documented one, and if it’s not documented it’s not safe to use anyways because it’s probably experimental and subject to change or removal in a later release. 