1

I have been working with C for the first time in a long time and one of the biggest problems for me has been working with strings, since they aren't expressed as well as they are in Python.

From what I know and understand, a char * is just a pointer to a string(or rather, the first character in a string). A char[] is very similar and can be used the same way.

My first question is a little side question, but while we use it to execute the same things, is there a difference in correctness or how the compiler views it?

Going ahead, I know that char *[] is just an array, but each element is a pointer of type char *. So through that each element when deferenced/accessed would just return a string. Which is why char *argv[] just takes values from command line.

For a problem that I was working on I needed a a 2D array of strings and had been trying to run it is char *[][] and making function calls for it.

I have a function type defined as void runoff_function(candidates *, int a, int b,char * array[a][b]); That expects a 2D array of character pointers.

My main function has a variable defined and populated as char* list[n][argc];

Except when running a loop to initialize user inputs:

char* list[n][argc];
    for(int i=0;i<n;i++)
    {
        printf("Voter %d\n",(i+1));

        for(int j=1;j<argc;j++)
        {
            printf("Rank %d\t",j);
            scanf("%s",list[i][j-1]);
    }

I get a seg fault after my first input and I don't know why.

  • You need to reserve space for each element of the array, in other words, you are scanning to a non allocated space, in consequence you get a segfault. Solution: use `list[i][j - 1] = malloc(ELEM_MAX_LENGTH);` before the `scanf` line. – David Ranieri Sep 11 '20 at 07:08
  • char* is just pointer to array, not an actual array. In your case you should allocate space first like this `char list[n][argc][MAX_SIZE]` - MAX_SIZE is maximum size of each string in 2D array – Elbek Sep 11 '20 at 07:12
  • @Elbek Answers two this question: https://stackoverflow.com/a/19824060/8141772 had indicated using a pointer would make more sense? – Mr. Johnny Doe Sep 11 '20 at 07:23
  • @Mr.JohnnyDoe, but in this example, the values are given in declaration step. C compiler will create an array with given values' size and pointer would point to that array. – Elbek Sep 11 '20 at 07:25
  • So in this case, since every value is user defined, one element at a time, I would have to approach it differently and an array representation would work better? @Elbek – Mr. Johnny Doe Sep 11 '20 at 07:28
  • Yeah, you can use my approach or @DavidRanieri approach as well. – Elbek Sep 11 '20 at 07:31

1 Answers1

2

The declaration char* list[n][argc]; reserves space for the string pointers, only. However, each string needs a place to store its characters. You must supply this space.

The easiest and safest way to do it, is to instruct scanf() to allocate some space on the heap for your string. This is done by adding the "m" modifier to the "%s" conversion. scanf() will then expect a char** as the argument, and store the pointer to a new string at that location. Your code would look like this:

scanf("%ms", &list[i][j-1]);

Note that it is your job to subsequently get rid of the memory allocations. So, once you are done with your strings, you will need to add a loop that calls free() on each cell of the 2D array:

for(int i=0;i<n;i++) {
    for(int j=1;j<argc;j++) {
        free(list[i][j-1]);
    }
}

The "%ms" format is specified by the POSIX.1-2008 standard, so safe to use on any modern linux.

cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • 1
    @DavidRanieri Good point about mentioning standards compliance. However, even though it's not in the C standard, my `man` page tells me that it's part of the POSIX.1-2008 standard. Which is almost as good. – cmaster - reinstate monica Sep 11 '20 at 07:31
  • POSIX isn't particularly good, because only *nix systems follow it. A standard that only a couple of OS follows isn't a standard. – Lundin Sep 11 '20 at 08:01
  • @Lundin Afaik, the only relevant, not POSIX compliant system is Windows, and that doesn't even ship with a standard compliant C compiler. I don't own a Windows box, so my information may not be correct or up-to-date. The vast majority of other systems (linux based, BSDs, MacOS) ships with a C compliant compiler (`gcc` or `clang`) and a POSIX compliant libc (I don't know about Apple). – cmaster - reinstate monica Sep 11 '20 at 08:21
  • POSIX was designed by "lets take this OS that we already have designed, then call it standard and show it down everyone else's throats". Exactly the kind of behavior we often accuse Microsoft for, but in this case they were not the offending party for once. Also, POSIX often collides with C programming - to call it a harmonized standard would be a lie. Personally, I mostly code freestanding systems so I couldn't care less about POSIX. – Lundin Sep 11 '20 at 08:29
  • @Lundin Well, I guess that there are quite a few questionable decisions made in the POSIX standard, and yes, it was obviously designed to match what UNIX did. Nevertheless, adding the `"m"` modifier for the `scanf()` formats is a very useful and sane decision, imho. And POSIX does serve as a lowest common denominator in the UNIX world, including such niche products like AIX. POSIX is more ubiquitous than LINUX is, it is one of the standards with the broadest adaption, right after the C standard itself. If you don't care about it, that's your choice. Most non-Windows developers work with POSIX. – cmaster - reinstate monica Sep 11 '20 at 09:08
  • Eh... `scanf` isn't useful or sane to begin with, it might be the most harmful function ever written, for any programming language. I agree that it is handy to allocate memory directly from the input function, but they could have done that by introducing a less dysfunctional function. But lets combine format string bugs + buffer overrun bugs + va_list non-existent type safety bugs with memory leak bugs... what can go wrong. – Lundin Sep 11 '20 at 09:17
  • @Lundin With compilers checking argument types based on format strings, the point about format string bugs is mute. With compilers allowing you to specify derived format taking function signatures, the va_list non-existent type safety bugs point is mute. With allocating formats, the buffer overrun bugs point is mute. That leaves only the memory leak bugs, which are inherent to C. I fully agree that `scanf("%s")` was an abysmal idea from the off. But `scanf("%ms")` fixed that, and it is one of the sanest ways to perform input in C. – cmaster - reinstate monica Sep 11 '20 at 10:14
  • "the point about format string bugs is mute" ...until the point where it is stored in a variable or resolved in run-time. `scanf` is an inherently brain-damaged function and there's no point to argue about that, because everyone who has used it knows it - and have written numerous bugs related to it. Compilers trying to pick up the pieces by providing static analysis might happen, but that doesn't make the function itself any less bad. – Lundin Sep 11 '20 at 10:37
  • In that case, I think we should just agree to disagree. You argue only from the definition of `scanf()` itself and the evil things that it allows a programmer to do. That's fine. I argue from what I actually experience as a programmer with proper tool support (my compiler warns me when a format string is not a literal that it can check, and I heed its warning), which I believe is also a valid standpoint. Both views are valid and fine, and we shouldn't be fighting over them :-) – cmaster - reinstate monica Sep 11 '20 at 10:47
  • I have proper tool support: my static analyser gives me an error for attempting to use `stdio.h` in production code and rightly so. – Lundin Sep 11 '20 at 10:49