Returning an array of strings in C

K

kleary00

Hi,

I am writing a function that needs to return an array of strings and I
am having some trouble getting it right. I need some help.

Here is what I consider an array of 100 strings:
char *array_string[100]

Thanks,
-Kim
 
K

Klarth

Your return type is char**, then in your function use:

function name (args ...)
{
...
return array_string;
}
 
C

CBFalconer

I am writing a function that needs to return an array of strings
and I am having some trouble getting it right. I need some help.

Here is what I consider an array of 100 strings:
char *array_string[100]

#include <stdio.h>
#include "ggets.h"
#define MAXSTR 100

int main(void)
{
char *array_strp[MAXSTR+1]; /* +1 allows for terminal NULL */
char *bfr;
char **dumper;
int i;

i = 0;
while ((i < MAXSTR) && (0 == ggets(&bfr))) {
array_strp[i++] = bfr;
}
array_strp = NULL;

/* dump 'em all */
dumper = &array_strp[0];
while (*dumper) puts(*dumper++);
return 0;
} /* tested */

will fill the string array from lines read from stdin. You can
find ggets at:

<http://cbfalconer.home.att.net/download/>

(You can also use fggets from the same package to read from any
arbitrary file)

--
<http://www.cs.auckland.ac.nz/~pgut001/pubs/vista_cost.txt>
<http://www.securityfocus.com/columnists/423>

"A man who is right every time is not likely to do very much."
-- Francis Crick, co-discover of DNA
"There is nothing more amazing than stupidity in action."
-- Thomas Matthews
 
R

Richard Heathfield

(e-mail address removed) said:
Hi,

I am writing a function that needs to return an array of strings and I
am having some trouble getting it right. I need some help.

Here is what I consider an array of 100 strings:
char *array_string[100]

You have three problems here:

1) you have been poorly-served by the answers you've received so far,
one of which gave you misinformation and the second of which ignored
your question completely in what seems a rather strange attempt to
"push" a non-standard function onto you (what ya playin' at, Chuck?);

2) what you consider an array of 100 strings is not in fact an array of
100 strings, but an array of 100 pointers to char - it can, however, be
used for storing 100 pointers to chars that are the first characters of
strings, so it's not a bad starting place, but you have to consider
memory issues (see below);

3) in C, there is no syntax for returning an array from a function.

You could instead:

a) create the memory for the array within the function itself, and
return a pointer to the address of that array's first element;
b) pass the memory for the array into the function via a parameter;
c) bypass the modularity principle completely.

c) is never good if there's some other way, so your best choice is
between a) and b).

You also need to make it clear where the strings come from, and whether
you just want to point to them or make actual copies of them. It makes
a difference when setting up an example.
 
C

CBFalconer

Richard said:
.... snip ...

1) you have been poorly-served by the answers you've received so
far, one of which gave you misinformation and the second of which
ignored your question completely in what seems a rather strange
attempt to "push" a non-standard function onto you (what ya
playin' at, Chuck?);

I (naturally) disagree. I used his storage specification, and
arranged to populate it. This requires either a) an independent
char* pointer, which can be freely copied, or b) elaborate
mechanisms to do deep copies from an input buffer or c) extensive
boring code of the form a[i++] = "tedious example string". ggets
fits version a) with no effort, as witnessed by the simplicity of
the loading code. He now has a populated buffer to deal with as he
will, and can use any old text file to fill it. He can pass that
array around with standard C semantics to whatever processing
routines he wishes.

--
<http://www.cs.auckland.ac.nz/~pgut001/pubs/vista_cost.txt>
<http://www.securityfocus.com/columnists/423>

"A man who is right every time is not likely to do very much."
-- Francis Crick, co-discover of DNA
"There is nothing more amazing than stupidity in action."
-- Thomas Matthews
 
R

Richard Heathfield

CBFalconer said:
I (naturally) disagree.

<sigh> "I am writing a function that needs to return an array of
strings", quoth the OP. Since you disagree with my claim that you
ignored the question, could you please explain how your original answer
(or, indeed, your subsequent answer) addresses that question? Because
I, for one, just don't see it.
 
D

DanielJohnson

(e-mail address removed) said:
I am writing a function that needs to return an array of strings and I
am having some trouble getting it right. I need some help.
Here is what I consider an array of 100 strings:
char *array_string[100]

You have three problems here:

1) you have been poorly-served by the answers you've received so far,
one of which gave you misinformation and the second of which ignored
your question completely in what seems a rather strange attempt to
"push" a non-standard function onto you (what ya playin' at, Chuck?);

2) what you consider an array of 100 strings is not in fact an array of
100 strings, but an array of 100 pointers to char - it can, however, be
used for storing 100 pointers to chars that are the first characters of
strings, so it's not a bad starting place, but you have to consider
memory issues (see below);

3) in C, there is no syntax for returning an array from a function.

You could instead:

a) create the memory for the array within the function itself, and
return a pointer to the address of that array's first element;
b) pass the memory for the array into the function via a parameter;
c) bypass the modularity principle completely.

c) is never good if there's some other way, so your best choice is
between a) and b).

You also need to make it clear where the strings come from, and whether
you just want to point to them or make actual copies of them. It makes
a difference when setting up an example.

If I understand it correctly, allocate memeory using malloc in the
fucntion and return char** type. Please correct me if I am wrong.
 
R

Richard Heathfield

DanielJohnson said:

If I understand it correctly, allocate memeory using malloc in the
fucntion and return char** type. Please correct me if I am wrong.

Okay, let's not worry about the data source for now, and just focus on
the return technique.

First, the bad you can't return an array from a C function.

Now, the good you can fake it.

Let's start off with a generic demonstration, using a type T (just some
type or other):

#include <stdlib.h>
#include <stdio.h>

#include "t.h" /* no, this doesn't exist - we're faking it for now */

T *t_create(size_t n, FILE *in)
{
T *new = malloc(n * sizeof *new); /* space for n objects of type T */
if(new != NULL)
{
size_t i = 0;
while(i < n)
{
/* give new a value in some program-specific manner */
t_populate_from_file(&new[i++], in);
}
}
return new;
}

First, we create the storage space. Then we check that it worked. Then
we populate the array. Then we return a pointer to its first element.
DO NOT modify 'new' in any way in this function - its value is
important, as it marks the start of the memory space allocated to you.

You'd collect the array something like this:

T *lots_of_t = t_create(42, stdin);
if(lots_of_t != NULL)
{
use the array here, and when you've eventually finished, you:

free(lots_of_t); /* CAUTION! See below. */


If T is a simple type, e.g. an int or a double or a struct full of only
simple types, then you can free it as shown.

That's the general method. Now for strings it gets a bit more
complicated, because we need to allocate space for the strings
themselves.

Let's start off with a simple routine for creating a fresh copy of a
string by allocating storage:

#include <string.h>
#include <stdlib.h>

char *dupstr(const char *s)
{
size_t len = strlen(s) + 1;
char *new = malloc(len * sizeof *new);
if(new != NULL)
{
memcpy(new, s, len);
}
return new;
}

Strings thus created can be released by free().

Now we are in a position to write our array allocator. I've pinched
t_create and hacked it, replacing T with char * (which means that T *
is replaced with char * *, of course):

char * * str_create(size_t n)
{
char * *new = malloc(n * sizeof *new);
if(new != NULL)
{
size_t i = 0;
while(i < n)
{
new[i++] = dupstr("Mary had a little lamb");
}
}
return new;
}

This'll give you n copies of "Mary had a little lamb", of course. You
can hack the loop to your tastes. Bear in mind that any or all of new's
members may have the value NULL instead of a pointer to a block of
memory containing "Mary had a little lamb", but that's the risk you
take when allocating memory dynamically. Be Prepared.
 
D

DanielJohnson

DanielJohnson said:

If I understand it correctly, allocate memeory using malloc in the
fucntion and return char** type. Please correct me if I am wrong.

Okay, let's not worry about the data source for now, and just focus on
the return technique.

First, the bad you can't return an array from a C function.

Now, the good you can fake it.

Let's start off with a generic demonstration, using a type T (just some
type or other):

#include <stdlib.h>
#include <stdio.h>

#include "t.h" /* no, this doesn't exist - we're faking it for now */

T *t_create(size_t n, FILE *in)
{
T *new = malloc(n * sizeof *new); /* space for n objects of type T */
if(new != NULL)
{
size_t i = 0;
while(i < n)
{
/* give new a value in some program-specific manner */
t_populate_from_file(&new[i++], in);
}
}
return new;

}

First, we create the storage space. Then we check that it worked. Then
we populate the array. Then we return a pointer to its first element.
DO NOT modify 'new' in any way in this function - its value is
important, as it marks the start of the memory space allocated to you.

You'd collect the array something like this:

T *lots_of_t = t_create(42, stdin);
if(lots_of_t != NULL)
{
use the array here, and when you've eventually finished, you:

free(lots_of_t); /* CAUTION! See below. */

If T is a simple type, e.g. an int or a double or a struct full of only
simple types, then you can free it as shown.

That's the general method. Now for strings it gets a bit more
complicated, because we need to allocate space for the strings
themselves.

Let's start off with a simple routine for creating a fresh copy of a
string by allocating storage:

#include <string.h>
#include <stdlib.h>

char *dupstr(const char *s)
{
size_t len = strlen(s) + 1;
char *new = malloc(len * sizeof *new);
if(new != NULL)
{
memcpy(new, s, len);
}
return new;

}

Strings thus created can be released by free().

Now we are in a position to write our array allocator. I've pinched
t_create and hacked it, replacing T with char * (which means that T *
is replaced with char * *, of course):

char * * str_create(size_t n)
{
char * *new = malloc(n * sizeof *new);
if(new != NULL)
{
size_t i = 0;
while(i < n)
{
new[i++] = dupstr("Mary had a little lamb");
}
}
return new;

}

This'll give you n copies of "Mary had a little lamb", of course. You
can hack the loop to your tastes. Bear in mind that any or all of new's
members may have the value NULL instead of a pointer to a block of
memory containing "Mary had a little lamb", but that's the risk you
take when allocating memory dynamically. Be Prepared.


So in the above example, you have essentially have 2D array of
characters and you wrote a str_create to allocate a 1D array and you
call it N times. So new[] contains pointers to all the different
strings in the 2D array which happen to be the same ("Mary had a
little lamb). I hope I got it right.


I wrote this int variant of it. Does it make sense ?

#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int nrows = 5000; /* Both nrows and ncols could be evaluated */
int ncols = 1000; /* or read in at run time */
int row;
float **Ar;
Ar = (float **) calloc(nrows, sizeof(float *));
if (Ar == NULL)
{
puts("\nFailure to allocate room for row pointers.\n");
exit(0);
}


for (row = 0; row < nrows; row++)
{
Ar[row] = (float *) calloc(ncols, sizeof(float *));

if (Ar[row] == NULL)
{
printf("\nFailure to allocate for row[%d]\n",row);
exit(0);
}
}

for(int i=0;i<nrows;i++)
{
for(int j=0;j<ncols;j++)
{
printf("\n Ar[%d][%d] = %d",i, j, Ar[j]);
}
}
return 0;
}


A quick question...what is the correct notation for writing pointers
1. char** p
2. char **p
3. char * *p; (you used this one);

Or are all of these permitted ?
 
R

Richard Heathfield

DanielJohnson said:

So in the above example, you have essentially have 2D array of
characters and you wrote a str_create to allocate a 1D array and you
call it N times.

Yes, although there is no requirement (as there would be in a strict 2D
array, char foo[X][Y]) for all the strings to be the same length.
There's an array of char *, and each element in that array points to
the first character in an array of char.
So new[] contains pointers to all the different
strings in the 2D array which happen to be the same ("Mary had a
little lamb). I hope I got it right.
Yes.

I wrote this int variant of it. Does it make sense ?

Looks more like a float variant to me.
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int nrows = 5000; /* Both nrows and ncols could be evaluated */
int ncols = 1000; /* or read in at run time */
Correct.

int row;
float **Ar;

In general, use double unless you have a very compelling reason to use
float. Here, you are allocating five million of them - that's typically
twenty MB on a modern desktop system - and this probably constitutes a
very compelling reason, as doubles would (again, on a modern desktop
system) typically take twice as much storage.
Ar = (float **) calloc(nrows, sizeof(float *));

Drop the cast, lose the types completely:

Ar = calloc(nrows, sizeof *Ar);
if (Ar == NULL)
{
puts("\nFailure to allocate room for row pointers.\n");
exit(0);
}

Good enough for student work. In a "real program", you might want to try
a bit harder to achieve the task despite tight memory constraints, but
that can wait for now.
for (row = 0; row < nrows; row++)
{
Ar[row] = (float *) calloc(ncols, sizeof(float *));

Ar[row] = calloc(ncols, sizeof *Ar[row]);
if (Ar[row] == NULL)
{
printf("\nFailure to allocate for row[%d]\n",row);
exit(0);

Yeah, that's okay.
}
}

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

This syntax works in C99, but almost nobody has a conforming C99
compiler. If you enable the C99 support a compiler /does/ have, you
might be giving up the ability to check for some C90 violations, which
might be an issue for you - and you're certainly giving up portability
to C90.
{
for(int j=0;j<ncols;j++)
{
printf("\n Ar[%d][%d] = %d",i, j, Ar[j]);


They're floats, so use %f, not %d, for the third arg:

printf("\n Ar[%d][%d] = %f",i, j, Ar[j]);
}
}
return 0;
}


A quick question...what is the correct notation for writing pointers
1. char** p
2. char **p
3. char * *p; (you used this one);

Or are all of these permitted ?

They are all legal. I normally use char **p, but in this case I wanted
to draw extra attention to the extra * for your benefit, to show the
isomorphism between:

T *t_create( ... )

and:

char * *str_create( ... )

That is, "T" and "char *" perform the same role here.
 
D

DanielJohnson

DanielJohnson said:

So in the above example, you have essentially have 2D array of
characters and you wrote a str_create to allocate a 1D array and you
call it N times.

Yes, although there is no requirement (as there would be in a strict 2D
array, char foo[X][Y]) for all the strings to be the same length.
There's an array of char *, and each element in that array points to
the first character in an array of char.
So new[] contains pointers to all the different
strings in the 2D array which happen to be the same ("Mary had a
little lamb). I hope I got it right.
Yes.

I wrote this int variant of it. Does it make sense ?

Looks more like a float variant to me.


#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int nrows = 5000; /* Both nrows and ncols could be evaluated */
int ncols = 1000; /* or read in at run time */
Correct.

int row;
float **Ar;

In general, use double unless you have a very compelling reason to use
float. Here, you are allocating five million of them - that's typically
twenty MB on a modern desktop system - and this probably constitutes a
very compelling reason, as doubles would (again, on a modern desktop
system) typically take twice as much storage.
Ar = (float **) calloc(nrows, sizeof(float *));

Drop the cast, lose the types completely:

Ar = calloc(nrows, sizeof *Ar);
if (Ar == NULL)
{
puts("\nFailure to allocate room for row pointers.\n");
exit(0);
}

Good enough for student work. In a "real program", you might want to try
a bit harder to achieve the task despite tight memory constraints, but
that can wait for now.
for (row = 0; row < nrows; row++)
{
Ar[row] = (float *) calloc(ncols, sizeof(float *));

Ar[row] = calloc(ncols, sizeof *Ar[row]);
if (Ar[row] == NULL)
{
printf("\nFailure to allocate for row[%d]\n",row);
exit(0);

Yeah, that's okay.
for(int i=0;i<nrows;i++)

This syntax works in C99, but almost nobody has a conforming C99
compiler. If you enable the C99 support a compiler /does/ have, you
might be giving up the ability to check for some C90 violations, which
might be an issue for you - and you're certainly giving up portability
to C90.
{
for(int j=0;j<ncols;j++)
{
printf("\n Ar[%d][%d] = %d",i, j, Ar[j]);


They're floats, so use %f, not %d, for the third arg:

printf("\n Ar[%d][%d] = %f",i, j, Ar[j]);
}
}
return 0;
}
A quick question...what is the correct notation for writing pointers
1. char** p
2. char **p
3. char * *p; (you used this one);
Or are all of these permitted ?

They are all legal. I normally use char **p, but in this case I wanted
to draw extra attention to the extra * for your benefit, to show the
isomorphism between:

T *t_create( ... )

and:

char * *str_create( ... )

That is, "T" and "char *" perform the same role here.


Many thanks.

One question but something not related to C, about the ethics of
posting in usenet. In past couple of times I top posted ignorantly and
then realized it was messing up the order of the other viewers. I am
using google group to post.

Are there any good newsgroup readers in Linux and Windwos. I tried PAN
but some network error won't let it connect.

If you could point me to some FAQ or some sticky in forum, it will be
of great help.
 
K

Keith Thompson

DanielJohnson said:
Many thanks.

One question but something not related to C, about the ethics of
posting in usenet. In past couple of times I top posted ignorantly and
then realized it was messing up the order of the other viewers. I am
using google group to post.

Something else to be aware of: it's rarely necessary or wise to quote
the entire article to which you're replying. Quote just what's
necessary for your followup to make sense, and trim the rest. In
particular, don't quote signatures (the stuff after the "-- "
delimiter) unless you're actually commenting on them.
Are there any good newsgroup readers in Linux and Windwos. I tried PAN
but some network error won't let it connect.

If you could point me to some FAQ or some sticky in forum, it will be
of great help.

There are a number of newsreaders. (I use Gnus, which runs under the
GNU Emacs text editor; it may not be to your taste.) To use one of
them, you need access to an NNTP server; check with your Internet
provider, or find a free or cheap one. A quick Google search found
http://www.newsreaders.com/; it looks like it has some good
information, but I can't vouch for it.

news.software.readers is probably a good place for further questions
(this isn't really topical here), but browse the archives before
posting there (as you should on any newsgroup).

It's possible to post properly using Google Groups, but more difficult
than it should be.
 
S

santosh

DanielJohnson said:
Many thanks.

One question but something not related to C, about the ethics of
posting in usenet. In past couple of times I top posted ignorantly and
then realized it was messing up the order of the other viewers. I am
using google group to post.

As far as I can tell, Google Groups automatically quotes the message
to which you're replying. I usually trim irrelevant material and
include my text either below the quoted message or interspersed with
it. It's easy to do so, once you've posted a few times.
Are there any good newsgroup readers in Linux and Windwos. I tried PAN
but some network error won't let it connect.

If I use a newsreader, (seldom), I use KDE's newsreader KNode. I've
also tried Pan, Thunderbird and Gnus. More importantly you need access
to a Usenet server, either a free one or via a subscription. As of
this writing the servers offered by aioe.org, gmane.org and dotsrc.org
appear to be free, (as in beer).

<snip>
 
R

Roberto Waltman

DanielJohnson said:
...
Are there any good newsgroup readers in Linux and Windwos. I tried PAN
but some network error won't let it connect.

If you could point me to some FAQ or some sticky in forum, it will be
of great help.

[OT] I can recommend Agent ( http://www.forteinc.com/ )
I used version 3.2 first in Windows and now in Linux. They offer only
a Windows version, but it runs under Wine.
(No experience with 4.??, their latest and greatest.)
There is a dedicated newsgroup: alt.usenet.offline-reader.forte-agent

Roberto Waltman

[ Please reply to the group,
return address is invalid ]
 
C

CBFalconer

Richard said:
CBFalconer said:

<sigh> "I am writing a function that needs to return an array of
strings", quoth the OP. Since you disagree with my claim that you
ignored the question, could you please explain how your original answer
(or, indeed, your subsequent answer) addresses that question? Because
I, for one, just don't see it.

I thought I just did. But you snipped it.

--
<http://www.cs.auckland.ac.nz/~pgut001/pubs/vista_cost.txt>
<http://www.securityfocus.com/columnists/423>

"A man who is right every time is not likely to do very much."
-- Francis Crick, co-discover of DNA
"There is nothing more amazing than stupidity in action."
-- Thomas Matthews
 
R

Richard Heathfield

DanielJohnson said:

Are there any good newsgroup readers in Linux and Windwos.

I use KNode under Linux. It's not perfect - it crashes a fair bit (and I
lack the time or enthusiasm to go fix it!), it doesn't seem to support
offline reading, and its "scoring" system is to killfiles as confetti
is to cricket bats - but it does the job.

For Windwos, you might want to try Thudnerbird.
 
A

Andrew Poelstra

DanielJohnson said:



I use KNode under Linux. It's not perfect - it crashes a fair bit (and I
lack the time or enthusiasm to go fix it!), it doesn't seem to support
offline reading, and its "scoring" system is to killfiles as confetti
is to cricket bats - but it does the job.

For Windwos, you might want to try Thudnerbird.

Thunderbird is also available for Linux.

The Evolution mail program works very well now for newsreading, as does
the text-based slrn program. emacs has an extension called gnus that
works very well, although it is difficult to install.

I have no idea how to do killfiles in any of them, and I don't
particularly care (email spam is a bigger concern).
 
R

Richard Heathfield

CBFalconer said:
I thought I just did. But you snipped it.

I don't want to belabour this point *too* much, since the OP's need
appears to have been met. Here's what I snipped from your previous
reply:

"I used his storage specification, and
arranged to populate it. This requires either a) an independent
char* pointer, which can be freely copied, or b) elaborate
mechanisms to do deep copies from an input buffer or c) extensive
boring code of the form a[i++] = "tedious example string". ggets
fits version a) with no effort, as witnessed by the simplicity of
the loading code. He now has a populated buffer to deal with as he
will, and can use any old text file to fill it. He can pass that
array around with standard C semantics to whatever processing
routines he wishes."

How, precisely, does this address the OP's question? Just to remind you,
he's trying to return an array of strings from a function. I submit
that your response does not in fact answer this question.

If I haven't made this clear enough by now, I will never be able to, so
(depending on how you reply) this may - in the interests of continuing
amity - be my last post in this subthread.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,764
Messages
2,569,564
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top