简体   繁体   中英

Extra characters when printing 2d array

I am having trouble getting rid of extra characters when I print the array. I am not sure what I am doing wrong. the output adds random characters to the end of lines. Any help is much appreciated.

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

int main(int argc, char **argv){
    int i=0;
    char c;
    const char * ch;
    int num_lines = 0;
    int k = 0;

    FILE *fpin;
    if(argc > 1){
        fpin = fopen(argv[1], "r");  
    }
    if (fpin == NULL){
        printf("Cannot Open File");
        return(0);
    }
    while(!feof(fpin)){
        c = getc(fpin);
        if(c == '\n'){
            num_lines++;
        }
     }
    printf("%d", num_lines);

    char **result = malloc(sizeof(char) * num_lines + 2);
    for(i=0;i<=num_lines;i++){
        result[i] = malloc(1000*sizeof(char));
    }

    i=0;
    rewind(fpin);   
    while(i<num_lines){
        c = getc(fpin);
        ch = &c;
        printf("%c", c);
        if (c == '\n'){
            strncat(result[i], "\0", 1);
            i++;
        }
        if(result[i] == NULL){
            printf("bad memory");
            break;
        } else {
            strncat(result[i], ch, sizeof(char));
        }
    }

    for(i=0;i<=num_lines;i++){

        printf("%s", result[i]);

     }
}

This line is not right.

char **result = malloc(sizeof(char) * num_lines + 2);

It should be:

char **result = malloc(sizeof(char*) * num_lines + 2);

Remember that you are trying to allocate num_lines char* , not num_lines char s. I am not sure why you have that additional + 2 in there. You just need:

char **result = malloc(sizeof(char*) * num_lines);
for(i=0;i<num_lines;i++){  /* Note the use of < and not <= */
    result[i] = malloc(1000*sizeof(char));
}

You can further simplify the for loop to:

for(i=0;i<num_lines;i++){
    result[i] = malloc(1000); /* sizeof(char) is always 1 */
}

Other answers have pointed out most of the major issues, and @David C. Rankin brought up a key point in his comment:

Your result[i] is not null terminated when you call strncat. As such, strncat has no idea where to put it.

To summarize:

char **result = malloc(sizeof(char *) * (num_lines + 1));  // Proper allocation

for(i = 0; i <= num_lines; i++)
{
    result[i] = calloc(1000, sizeof(char));
}

One last bit I would add is to skip appending the newline character onto the lines as you rebuild them:

while(i <= num_lines)
{
   // ...
    if(c == '\n')
    {
        i++;
    }
    else if(result[i] == NULL) // <== Note the else
    {
    // bad memory, I guess?
    }
    else
    {
        strncat(result[i], ch, 1);
    }
}

Then add it back when printing:

print("%s\n", result[i]);

The following way of memory allocation leads to segmentation fault .

char **result = malloc(sizeof(char) * num_lines + 2); //Instead of sizeof(char) you should use sizeof(char *).
for(i=0;i<num_lines;i++){
    result[i] = malloc(1000*sizeof(char));
}

Instead of this try the following-

char **result = malloc(sizeof(char *) * num_lines);
for(i=0;i<num_lines;i++){
    result[i] = malloc(1000*sizeof(char));
}

Then the remaining Part of the program is working fine. No need to change anything.

But When you are allocating memory dynamically Don't forget to free the memory at end.

for(i=0;i<num_lines;i++){
    printf("%s", result[i]);
 }

free(result); // It frees the allocated memory

[NOTE]: When you are using Command line arguments, You should check the input's from the user and if the necessary input's are not provided, you need to print Usage message. else it leads to Segmentation fault

if(argc!=2){
printf("Usage: ./a.out Filename\n");
return 0;
}

So, strncat(dst, src, n) scans dst looking for a '\\0' , and then adds upto n characters from src , stopping at its terminating '\\0' , and then appends '\\0' . As well covered elsewhere, your problem was that you had not initialised each result[i] to be an empty string... which calloc() will do for you, though with a touch of over-kill.

Aside: it is very important to understand that C really does not have a string type. What it does have are arrays of character values, and a convention in which the character value '\\0' is a "string terminator". The strxxx() functions do useful "string like" things on arrays of char, using this convention... but when you use them, it is worth remembering that "there is no string" . (There is deep magic which allows pointers and arrays to very nearly, but not quite, be duals of each other. This allows char* to look rather like a "string", but it is just an illusion, however convenient and useful it may be... there is thin ice here, and if you do not tread carefully, you will be in trouble.)

Now, for each character read, your code does a strncat() , which will re-scan the current result[i] ... looking for the '\\0' , which I'm here to tell you is O(n^2) :-( (Unless some clever code caches the last known length.)

Further, for a line longer than 1000 characters (including its terminating '\\0' ), you will overrun the end of your result[i] .

Aside: C's greatest gift to mankind is the ease with which you can overun an array of char :-( Each and every time you touch a "string" you need to ask yourself "can this overrun ?", and then when you think you have the answer, ask again "can this overrun taking into account the '\\0' at the end" -- and concentrate hard on the difference between the length of the "string" (which does not include the '\\0' ) and the length of the char array (which does).

Further, if a '\\0' appears in the input, that would (effectively) chop off the rest of the line. You may have reasons to believe that this will never happen, of course. (Plenty of code has crashed and burned when the impossible went ahead and just happened !)

As noted elsewhere, you count the number of '\\n' , and (sort of) add 1 to give you the number of lines, which is fine unless the file ends in '\\n' . You could check whether the last character read in the scanning loop was '\\n' , to tell whether you have a trailing, unterminated line.

Here's another lesson... concentrate (hard) on the edge cases and loop termination conditions... your read loop is:

while(i<num_lines){
  ....
  if (c == '\n'){
    ....
    i++;
  }
....
}

So, since num_lines is actually the number of '\\n' , this won't read the trailing (unterminated by '\\n' ) line (if any).

Your output loop, on the other hand, has a subtley different termination condition:

for(i=0;i<=num_lines;i++){
  printf("%s", result[i]);
}

Which will be fine provided result[num_lines] has been initialised empty !

For the read loop you could:

l = 0 ;
i = 0 ;
while (!feof(fpin)) {
    c = getc(fpin);
    if (c == '\n') {
        result[i][l] = '\0' ;
        i += 1 ;
        l  = 0 ;
      }
    else if ((c != '\0') && (l < (1000 - 1))) {
        result[i][l] = c ;
        l += 1 ;
      }
  }

if (l != 0)
    num_lines += 1 ;

Note that at the end l == 0 indicates that the file ended in '\\n' (possibly followed by any number of '\\0' ), and conversely l != 0 indicates that there is a not-empty trailing line which is not terminated by '\\n' (ignoring any '\\0' ). This could be useful -- though not in your case, where you are outputting the result less all the '\\n' in any case.

This simply ignores '\\0' in the input and truncates lines to 999 characters. You might want to do something to handle those cases more completely.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM