C: How to convert a \0 char into a dot for printf output?

4 views
Skip to first unread message

Andreas Schnellbacher

unread,
May 18, 2018, 12:30:19 PM5/18/18
to
In NEPMD, I've a problem with a function that deletes a zero-terminated
string from a zero-terminated string list. Therefore I want to make the
zero-terminated values at several states visible for printf output.

I've tried to write a function that should convert each zero char into a
dot. The following function doesn't work and I don't know why.

The static prefix is because I tried to return a string. A typical value for
pszString is:

pszString = "string1\0string2\0string3\0"

That should be converted for printf output into:

"string1.string2.string3."

The output of the function below is always an empty string.

Do you have ideas?

// --------------------------------------------------------------------------
static PSZ _zerotodot( PSZ pszString)
{
PSZ p = pszString;

if (p != NULL)
{
// Convert \0 to . for printf output
while (*p)
{
if (*p == '\0')
*p = '.';
p++;
}
}

return p;
}
// --------------------------------------------------------------------------

--
Andreas Schnellbacher

James Moe

unread,
May 18, 2018, 1:26:34 PM5/18/18
to
On 05/18/2018 09:30 AM, Andreas Schnellbacher wrote:

> I've tried to write a function that should convert each zero char into a
> dot. The following function doesn't work and I don't know why.
>
It is because you are removing the string terminator.
You need to add a terminator to each string where you replace the
terminator so that downstream string processing works as expected.

--
James Moe
jmm-list at sohnen-moe dot com
Think.

Andreas Schnellbacher

unread,
May 18, 2018, 1:34:59 PM5/18/18
to
On 18.05.18 19:26, James Moe wrote:

> On 05/18/2018 09:30 AM, Andreas Schnellbacher wrote:
>
>> I've tried to write a function that should convert each zero char into a
>> dot. The following function doesn't work and I don't know why.
>
> It is because you are removing the string terminator.
> You need to add a terminator to each string where you replace the
> terminator so that downstream string processing works as expected.

Thanks. I see, that makes sense. Sure, that's why in another function
double-zero-terminated strings exist.

--
Andreas Schnellbacher

Lars Erdmann

unread,
May 18, 2018, 3:29:39 PM5/18/18
to
That's not really true. In C, if you define a string like

"String1\0String2\0"
then in fact, the C compiler will create a string that adds an
additional zero terminator after this string. Just as it does for any
string.
What you really need to do is check for the DOUBLE occurrence of a zero
terminator in order to find the end of the string. Something like this
(untested):

static PSZ _zerotodot( PSZ pszString)
{
PSZ p = pszString;

if (p != NULL)
{
while ((*p != 0) || (*(p+1) != 0)) // !a || !b == !(a && b)
{
if (*p == 0)
*p = '.';
p++;
}
*p = '.'; // treat the very last "visible" zero terminator
}

return pszString; // you want to return the conv. string, don't you ?
}

Andreas Schnellbacher

unread,
May 19, 2018, 8:29:53 AM5/19/18
to
On 18.05.18 21:29, Lars Erdmann wrote:

> static PSZ _zerotodot( PSZ pszString)
> {
> PSZ p = pszString;
>
> if (p != NULL)
> {
> while ((*p != 0) || (*(p+1) != 0)) // !a || !b == !(a && b)
> {
> if (*p == 0)
> *p = '.';
> p++;
> }
> *p = '.'; // treat the very last "visible" zero terminator
> }
>
> return pszString; // you want to return the conv. string, don't you?
> }

Thanks Lars, that works.

But I don't understand why pszString has to be returned at the end and not
p. Apparently I don't fully understand pointers. I'm a C beginner.

I still have a problem with the code: The original pszString is converted
to a dot-seprated string, also for the other functions that expect a
zero-terminated string. Of course, they produce garbage now.

The original pszString has to be let unchanged. How can I create a copy of
it just for printf output? (I'm not used in when strcpy or strdup should be
used. I've tried both and they don't work.)

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 19, 2018, 9:30:35 AM5/19/18
to
Sorry, that wasn't understandable:

On 19.05.18 14:29, Andreas Schnellbacher wrote:

> I still have a problem with the code: The original pszString is converted
> to a dot-seprated string, also for the other functions that expect a
> zero-terminated string. Of course, they produce garbage now.

Correction:

I still have a problem with the code: The original pszString is converted
to a dot-separated string, also for the other functions that expect a
zero-separated string. Of course, they produce garbage now.

--
Andreas Schnellbacher

Steven Levine

unread,
May 19, 2018, 10:43:48 AM5/19/18
to
On Sat, 19 May 2018 13:30:35 UTC, Andreas Schnellbacher
<as...@despammed.com> wrote:

Hi Andreas,

> I still have a problem with the code: The original pszString is converted
> to a dot-separated string, also for the other functions that expect a
> zero-separated string. Of course, they produce garbage now.

Perhaps it would help if you posted the code that fails to do the
delete. It's easy to be off by one if you are not familar with
pointers.

Steven


--
---------------------------------------------------------------------
Steven Levine <ste...@earthlink.bogus.net>
DIY/Warp/BlueLion etc. www.scoug.com www.arcanoae.com www.warpcave.com
---------------------------------------------------------------------

Peter Flass

unread,
May 19, 2018, 11:42:54 AM5/19/18
to
Andreas Schnellbacher <as...@despammed.com> wrote:
> On 18.05.18 21:29, Lars Erdmann wrote:
>
>> static PSZ _zerotodot( PSZ pszString)
>> {
>> PSZ p = pszString;
>>
>> if (p != NULL)
>> {
>> while ((*p != 0) || (*(p+1) != 0)) // !a || !b == !(a && b)
>> {
>> if (*p == 0)
>> *p = '.';
>> p++;
>> }
>> *p = '.'; // treat the very last "visible" zero terminator
>> }
>>
>> return pszString; // you want to return the conv. string, don't you?
>> }
>
> Thanks Lars, that works.
>
> But I don't understand why pszString has to be returned at the end and not
> p. Apparently I don't fully understand pointers. I'm a C beginner.

By the time you're done, p points to the end of the string, pszString
points to the start.

>
> I still have a problem with the code: The original pszString is converted
> to a dot-seprated string, also for the other functions that expect a
> zero-terminated string. Of course, they produce garbage now.
>
> The original pszString has to be let unchanged. How can I create a copy of
> it just for printf output? (I'm not used in when strcpy or strdup should be
> used. I've tried both and they don't work.)
>



--
Pete

Andreas Schnellbacher

unread,
May 19, 2018, 12:11:58 PM5/19/18
to
On 19.05.18 16:43, Steven Levine wrote:

> Perhaps it would help if you posted the code that fails to do the
> delete. It's easy to be off by one if you are not familar with
> pointers.

The code is quite complex. I would have done that, if it were easier.
But I don't have an idea now how to post a complete minimal example.

It's a replacement for usual OS/2 ini keys, where keys are placed as
usual under the application "RegKeys" and their paths additionally
under the application "RegContainer". The value of a RegContainer key
consists of all subpath values of that key. These single values have
trailing null chars as separators for easier processing.

The advantage of the RegContainer tree is that existing paths can be
queried without having to query and analyze an entire ini application.

I've found that on deleting a RegKey key, together with its
RegContainer key, can remove a null char too much. Therefore I want
to make such a key value visible, which is not possible without
converting the null chars.

If I would post that faulty function, I would have to post almost
everything of that part of the library, to make the code complete.

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 19, 2018, 12:13:51 PM5/19/18
to
On 19.05.18 17:42, Peter Flass wrote:

> By the time you're done, p points to the end of the string, pszString
> points to the start.

Thanks, I understand.

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 20, 2018, 7:37:49 AM5/20/18
to
On 19.05.18 18:11, Andreas Schnellbacher wrote:

[...]

In the meantime, I've found the bug in the NEPMD code:

http://trac.netlabs.org/nepmd/ticket/26
http://trac.netlabs.org/nepmd/changeset/3011

Lars' function works, but the original pointer is changed. Therefore
I can't use that pointer for further processing after having used
Lars' function once.

I've moved it in the code until I've found the place were the error
occurs. The bug was that the check

if (!pszEntry)

has to be replaced by

if (!*pszEntry)

to handle empty strings correctly. Sure, you can't understand that,
because the complete code block for reproducing this has 287 lines.

My other problem with creating a copy of a pointer for output
processing (e.g. Lars' function) and using the original pointer
for standard further processing still exists. I'd thought that
strdup should work, but that failed:

static PSZ _zerotodot( PSZ pszString)
{
APIRET rc = NO_ERROR;
PSZ pszCopy = strdup( pszString);
PSZ p = pszCopy;

if (!pszCopy)
{
rc = ERROR_NOT_ENOUGH_MEMORY;
return;
}

if (p != NULL)
{
while ((*p != 0) || (*(p + 1) != 0)) // !a || !b == !(a && b)
{
if (*p == 0)
*p = '.';
p++;
}
*p = '.'; // treat the very last "visible" zero terminator
}

// cleanup
//if (pszCopy) free( pszCopy);

return pszCopy;
}

Any idea what I did wrong?

--
Andreas Schnellbacher

Peter Flass

unread,
May 20, 2018, 10:36:47 AM5/20/18
to
Andreas Schnellbacher <as...@despammed.com> wrote:
> On 19.05.18 18:11, Andreas Schnellbacher wrote:
>
> [...]
>
> In the meantime, I've found the bug in the NEPMD code:
>
> http://trac.netlabs.org/nepmd/ticket/26
> http://trac.netlabs.org/nepmd/changeset/3011
>
> Lars' function works, but the original pointer is changed. Therefore
> I can't use that pointer for further processing after having used
> Lars' function once.
>
> I've moved it in the code until I've found the place were the error
> occurs. The bug was that the check
>
> if (!pszEntry)
>
> has to be replaced by
>
> if (!*pszEntry)
>
> to handle empty strings correctly. Sure, you can't understand that,
> because the complete code block for reproducing this has 287 lines.
>
> My other problem with creating a copy of a pointer for output
> processing (e.g. Lars' function) and using the original pointer
> for standard further processing still exists. I'd thought that
> strdup should work, but that failed:

Failed how?

>
> static PSZ _zerotodot( PSZ pszString)
> {
> APIRET rc = NO_ERROR;
> PSZ pszCopy = strdup( pszString);
> PSZ p = pszCopy;
>
> if (!pszCopy)
> {
> rc = ERROR_NOT_ENOUGH_MEMORY;
> return;
> }
>
> if (p != NULL)
> {
> while ((*p != 0) || (*(p + 1) != 0)) // !a || !b == !(a && b)
> {
> if (*p == 0)
> *p = '.';
> p++;
> }
> *p = '.'; // treat the very last "visible" zero terminator
> }
>
> // cleanup
> //if (pszCopy) free( pszCopy);
>
> return pszCopy;
> }
>
> Any idea what I did wrong?
>



--
Pete

Steven Levine

unread,
May 20, 2018, 11:49:40 AM5/20/18
to
On Sun, 20 May 2018 11:37:48 UTC, Andreas Schnellbacher
<as...@despammed.com> wrote:

Hi Andreas,

FWIW, you really should have pointed us explicitly to the nepmd repo
in the first place. It eliminates the need to post code to the thread
or elsewhere. I was a bit slow on the uptake and did not think of
this until late yesterday.

> In the meantime, I've found the bug in the NEPMD code:
>
> http://trac.netlabs.org/nepmd/ticket/26
> http://trac.netlabs.org/nepmd/changeset/3011

It appears you have a memory leak at:

libreg.c:514
else
// yes: remove this container entry
pszList = NULL;

which will cause the pszList not to be freed.

If I am reading the code right, you are not writing the list
terminator nul to the key value. This probably deserves a comment.

Andreas Schnellbacher

unread,
May 20, 2018, 12:46:37 PM5/20/18
to
On 20.05.18 17:49, Steven Levine wrote:

> FWIW, you really should have pointed us explicitly to the nepmd repo
> in the first place. It eliminates the need to post code to the thread
> or elsewhere. I was a bit slow on the uptake and did not think of
> this until late yesterday.

OK.

> It appears you have a memory leak at:
>
> libreg.c:514
> else
> // yes: remove this container entry
> pszList = NULL;
>
> which will cause the pszList not to be freed.

Thanks, I see, will commit as http://trac.netlabs.org/nepmd/changeset/3013

> If I am reading the code right, you are not writing the list
> terminator nul to the key value. This probably deserves a comment.

Steven, I hope I got you right. Yes, the string list is usually not
double terminated. E.g., when being saved to RegContainer, it's single
terminated. Christian had rather commented the parts where he allocates
1 additional byte.

The C code is from Christian and I have problems to understand all of
them. But anyhow, I try to find bugs as far as I can and as people help
me. It's great how much I could fix that way and how much of C I have
learned by that.

--
Andreas Schnellbacher

nospa...@efbe.prima.de

unread,
May 20, 2018, 1:06:32 PM5/20/18
to
Am 20.05.2018 um 13:37 schrieb Andreas Schnellbacher:

Hi Andreas,

[...]
> My other problem with creating a copy of a pointer for output
> processing (e.g. Lars' function) and using the original pointer
> for standard further processing still exists. I'd thought that
> strdup should work, but that failed:
>
> static PSZ _zerotodot( PSZ pszString)
> {
> APIRET rc = NO_ERROR;
> PSZ pszCopy = strdup( pszString);

The c stringhandling functions expect a standard c string which is
terminated by the first \0 char. So she strdup will only copy the first
string of your "string1\0string2\0..." example.
And you get a pointer, which has to be freed later on.
[...]
> // cleanup
> //if (pszCopy) free( pszCopy);

But not in this function, if it used as returnvalue...

>
> return pszCopy;
> }

CU/2
Frank

Andreas Schnellbacher

unread,
May 20, 2018, 5:30:55 PM5/20/18
to
On 20.05.18 19:06, nospa...@efbe.prima.de wrote:

> The c stringhandling functions expect a standard c string which is
> terminated by the first \0 char. So she strdup will only copy the first
> string of your "string1\0string2\0..." example.
> And you get a pointer, which has to be freed later on.
> [...]
>> // cleanup
>> //if (pszCopy) free( pszCopy);
>
> But not in this function, if it used as returnvalue...

That confirms what I thought.

>> return pszCopy;
>> }

Thanks, Frank. That was the info I was looking for.

BTW: Do you also have an idea, how to get it copied? (I guess with
memcpy.)

--
Andreas Schnellbacher

Peter Flass

unread,
May 20, 2018, 6:03:22 PM5/20/18
to
C string handling is painful at best. In this case, probably memcpy.
However, how do you identify the end of the string? Presumably, since it's
a concatenation of null-terminated strings the end of all should be
identified by two nulls in a row?

--
Pete

Lars Erdmann

unread,
May 21, 2018, 3:07:54 AM5/21/18
to
>>
>> BTW: Do you also have an idea, how to get it copied? (I guess with
>> memcpy.)
>>
>
> C string handling is painful at best. In this case, probably memcpy.
> However, how do you identify the end of the string? Presumably, since it's
> a concatenation of null-terminated strings the end of all should be
> identified by two nulls in a row?
>

Yes. And I am pretty sure, if you use a C++ library that can handle a
string with embedded zeros it is going to do the very same thing internally.
The C++ class library that comes with VAC has a string class that can
handle strings with embedded zeros (IString, also I0String).

Lars

Andreas Kohl

unread,
May 21, 2018, 3:15:56 AM5/21/18
to
Andreas Schnellbacher schrieb:
> In NEPMD, I've a problem with a function that deletes a zero-terminated
> string from a zero-terminated string list. Therefore I want to make the
> zero-terminated values at several states visible for printf output.
>
> I've tried to write a function that should convert each zero char into a
> dot. The following function doesn't work and I don't know why.

Does this question really deal with the C language? Integer character
constants have type int in C. Only the value of the literal '\0' in C++
should have type char.

--
Andreas

Andreas Schnellbacher

unread,
May 21, 2018, 5:30:42 AM5/21/18
to
On 21.05.18 00:03, Peter Flass wrote:

> C string handling is painful at best. In this case, probably memcpy.

Thanks Peter.

> However, how do you identify the end of the string? Presumably, since it's
> a concatenation of null-terminated strings the end of all should be
> identified by two nulls in a row?

There exist a length parameter: ulDataLen. I haven't reviewed the code
again where the string end is determined by that and where it's handled by
two nulls. But that one exists. At least the values that are stored in
NEPMD.INI -> RegContainer are single-null-terminated.

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 21, 2018, 5:35:26 AM5/21/18
to
On 21.05.18 09:05, Andreas Kohl wrote:

> Integer character constants have type int in C. Only the value of
> the literal '\0'

Lars had already pointed me to that error in my first version and
provided a working solution.

--
Andreas Schnellbacher

Steven Levine

unread,
May 21, 2018, 10:42:46 AM5/21/18
to
On Sun, 20 May 2018 16:46:36 UTC, Andreas Schnellbacher
<as...@despammed.com> wrote:

Hi,

> > If I am reading the code right, you are not writing the list
> > terminator nul to the key value. This probably deserves a comment.

> Steven, I hope I got you right.

I think we are understanding each other.

>Yes, the string list is usually not
> double terminated. E.g., when being saved to RegContainer, it's single
> terminated. Christian had rather commented the parts where he allocates
> 1 additional byte.

Where did the original code come from? It looks similar to some of
the profile code in Christian's WPSTK, but your code probably came
from elsewhere or was heavily adapted.

Steven Levine

unread,
May 21, 2018, 11:01:14 AM5/21/18
to
On Mon, 21 May 2018 09:30:40 UTC, Andreas Schnellbacher
<as...@despammed.com> wrote:

Hi,

> There exist a length parameter: ulDataLen. I haven't reviewed the code
> again where the string end is determined by that and where it's handled by
> two nulls.

ulDataLen is set by two different methods and has two slightly
different meanings.

ulDataLen is set via PATHENTRYLEN() which eventually calls
PrfQueryProfileSize() so it will be set to the actual size of the key
value in the profile file.

ulDataLen is also calculated using _getEndOfStrList() and will set
ulDataLen to the number of characters needed to hold the strings
following pszNextEntry. The calculated count will include the nul
terminators for each string and the nul terminator that signals the
end of the list of strings.

Andreas Schnellbacher

unread,
May 21, 2018, 12:38:01 PM5/21/18
to
On 21.05.18 16:42, Steven Levine wrote:

> Where did the original code come from? It looks similar to some of
> the profile code in Christian's WPSTK, but your code probably came
> from elsewhere or was heavily adapted.

The code comes from Christian of 2002. AFAIR, in WPSTK there exist only
text ini functions. (I haven't checked the not-exported functions.)
I had thought, up to a few years, that Christian had copied the code from
WPSTK to NEPMD, as he did with other functions. But he hadn't. He has
created the entire NEPMD lib in a few weeks and apparently missed to add
the config functions to WPSTK.

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 21, 2018, 12:41:39 PM5/21/18
to
On 21.05.18 17:01, Steven Levine wrote:

> ulDataLen is set by two different methods and has two slightly
> different meanings.
>
> ulDataLen is set via PATHENTRYLEN() which eventually calls
> PrfQueryProfileSize() so it will be set to the actual size of the key
> value in the profile file.
>
> ulDataLen is also calculated using _getEndOfStrList() and will set
> ulDataLen to the number of characters needed to hold the strings
> following pszNextEntry. The calculated count will include the nul
> terminators for each string and the nul terminator that signals the
> end of the list of strings.

Thanks for the explanation, Steven.

I've learned much in this thread. Thanks to everyone who helped me.

--
Andreas Schnellbacher

nospa...@efbe.prima.de

unread,
May 21, 2018, 4:57:00 PM5/21/18
to
Am 20.05.2018 um 23:30 schrieb Andreas Schnellbacher:

Hi Andreas,

>
> Thanks, Frank. That was the info I was looking for.
>
> BTW: Do you also have an idea, how to get it copied? (I guess with
> memcpy.)

Yes, but to use memcpy, you need the length. This can be calculated by a
variation of Lars code: untested


static int zerolength( PSZ pszzString)
{
PSZ p = pszzString;

if (p != NULL)
{
while ((*p != 0) || (*(p+1) != 0)) // !a || !b == !(a && b)
{

p++;


}
return (p +1 - pszzString); // calculate length
}
else
{
return 0; // length is zero
}
}

Now you can use the returned length for malloc and memcpy.

CU/2
Frank

Steven Levine

unread,
May 22, 2018, 11:00:49 AM5/22/18
to
On Mon, 21 May 2018 20:56:57 UTC, nospa...@efbe.prima.de wrote:

Hi Frank,

> Yes, but to use memcpy, you need the length. This can be calculated by a
> variation of Lars code: untested

No need for new code in this case. The library already includes
_getEndOfStrList which provides the needed functionality. Currently
it's only used to calculate the length of the tail of the list, but

ulDataLen = _getEndOfStrList( pszList) - pszList + 1;

would provide the count needed by memcpy.

Andreas Schnellbacher

unread,
May 22, 2018, 3:37:06 PM5/22/18
to
On 22.05.18 17:00, Steven Levine wrote:

> On Mon, 21 May 2018 20:56:57 UTC, nospa...@efbe.prima.de wrote:
>
> Hi Frank,
>
>> Yes, but to use memcpy, you need the length. This can be calculated by a
>> variation of Lars code: untested
>
> No need for new code in this case. The library already includes
> _getEndOfStrList which provides the needed functionality. Currently
> it's only used to calculate the length of the tail of the list, but
>
> ulDataLen = _getEndOfStrList( pszList) - pszList + 1;
>
> would provide the count needed by memcpy.

Thanks again to both of you.

--
Andreas Schnellbacher

Andreas Schnellbacher

unread,
May 22, 2018, 3:41:04 PM5/22/18
to
On 22.05.18 21:37, Andreas Schnellbacher wrote:

> On 22.05.18 17:00, Steven Levine wrote:
>
>> ulDataLen = _getEndOfStrList( pszList) - pszList + 1;
>
> Thanks again to both of you.

And for those who are interested: Here's the link to Christian's code:
http://svn.netlabs.org/repos/nepmd/trunk/src/gui/common/libreg.c

--
Andreas Schnellbacher

nospa...@efbe.prima.de

unread,
May 22, 2018, 3:44:49 PM5/22/18
to
Am 22.05.2018 um 17:00 schrieb Steven Levine:
> On Mon, 21 May 2018 20:56:57 UTC, nospa...@efbe.prima.de wrote:

Hello Steven,

>
> Hi Frank,
>
>> Yes, but to use memcpy, you need the length. This can be calculated by a
>> variation of Lars code: untested
>
> No need for new code in this case. The library already includes
> _getEndOfStrList which provides the needed functionality. Currently
> it's only used to calculate the length of the tail of the list, but
>
> ulDataLen = _getEndOfStrList( pszList) - pszList + 1;

I saw the function in the src, but didn't find the NEXTSTR macro, so I
provided a new function.

Andreas Schnellbacher

unread,
May 23, 2018, 5:56:07 PM5/23/18
to
And e.g. NEXTSTR is defined here:

http://svn.netlabs.org/repos/nepmd/trunk/src/gui/common/macros.h

--
Andreas Schnellbacher
Reply all
Reply to author
Forward
0 new messages