I have an assignment where we take in the contents from an input file and put it into arrys the numbers on the first line represent the dimensions for the array. the first 6 regards the first 6 rows/lines on the text file, which i have put in a one dimensional ray but the characters after that i have to put them in a 5x6 2d array in the form of a solved crossword (with the spaces included and making sure to have a n extra column for '\0'so 5x7) I have to use malloc or calloc to make the array and my issue is making the matrix as it doesnt get the spaces or i get a segmentation dump. Im trying to make the arrays first(two arrays - one dimensional array of W strings and a 2d array of characters NxM) and make sure the elements are in the matrix but like i said i keep getting a segmentation dump. Im at a loss as to how to do this. txt file:
6 5 6
nail
taco
name
men
next
can
next
namect
e aa
n nc
nail
here's my code so far:
void freeMatrix(char **matrix, int r, int c);
int main(int argc, char **argv)
{
int W, N, M;
char inBuf[SIZE], words[SIZE], crosswords[SIZE];
char *word;
char *outp;
FILE *input;
// FILE *output;
if (argc != 2)
{
printf("Error, please input the filenames\n");
exit(-1);
}
if ((input = fopen(argv[1], "r")) == NULL)
{
printf("Error, the file %s cannot be opened\n", argv[1]);
}
fgets(inBuf, SIZE, input);
sscanf(inBuf, "%i%i%i", &W, &N, &M);
word = (char *)malloc(sizeof(char) * W);
char *crossword[N];
char *ptr = word;
char token[N][M];
for (int x = 0; x < W; x )
{
fgets(inBuf, SIZE, input);
sscanf(inBuf, "%s", &words);
printf("%s\n", words);
*ptr = *words;
}
for (int f = 0; f < N; f )
{
crossword[f] = (char *)malloc(N * sizeof(char));
}
for (int y = 0; y < N; y )
{
for (int z = 0; z < M; z )
{
fscanf(input, "%c", &token[y][z]);
crossword[y][z] = token[y][z];
printf("%s", crossword[y][z]);
}
printf("\n");
}
fclose(input);
free(word);
freeMatrix(crossword, N, M);
return 0;
}
void freeMatrix(char **matric, int r, int c)
{
for (int i = 0; i < r; i )
{
free(matric[i]);
}
free(matric);
}
I expected a crossword to be printed with the individual characters in the matrix the same way the text file has it set up. A segmentation error resulted. It prints the first 6 words fine as an array of strings but the 2d array of characters is where i fall short.
nail
taco
name
men
next
can
Segmentation fault (core dumped)
CodePudding user response:
A lot of things wrong here. My first suggestion is just to get the easy bit working first. Read in the 6 words into an array and then print them out. Get that working first. (that isnt working in your code at the moment, you do not save the words then get entered anywhere). Here is whats wrong at the moment
fgets(inBuf, SIZE, input); // ok
sscanf(inBuf, "%i%i%i", &W, &N, &M); //ok
what is the purpose of word below? It is an array of 6 characters. There seems no point to it since 6 is the number of words, not the length of them
word = (char *)malloc(sizeof(char) * W);
char *crossword[N];
char *ptr = word;
// char token[N][M]; <<<< dont need this yet
// ok this loop read W words from the input file.
for (int x = 0; x < W; x )
{
fgets(inBuf, SIZE, input); // good
sscanf(inBuf, "%s", &words); // you could have fscanf directly but ok
printf("%s\n", words); // print it
*ptr = *words; // meaningless line
}
I think that last line is somehow trying to copy the word you just read into the buffer 'word' given that you did *ptr = word
earlier
First note the odd names, I would expect 'words' to be the array or wards and 'word' to be the one you just read.
More importantly thats not how you copy strings
So lets fix this
First we need an array of pointers to words
char *words[N];
I am reluctant to type that because its not standard c but you use it every where (Variable length arrays, my compiler does not allow it at all, even if I ask really nicely). But I will do it.
now we need a char buffer to read the current word into
char word[SIZE]; // get rid of old word and words
now lets do some reading
// get rid of old word and words
char word[SIZE];
char *words[N];
for (int x = 0; x < W; x )
{
fgets(inBuf, SIZE, input);
sscanf(inBuf, "%s", &word);
words[x] = strdup(word);
}
now
for (int x = 0; x < W; x )
{
printf("%s\n", words[x];
}
to prove we actually have the data
get all that workinig before moving on to the next section
PS, strdup is a wonderful function that mallocs the correct size for the string and then copies the string to the malloced buffer, returnig a pointer to the newly mallocd memory
CodePudding user response:
There are several common mistakes present in this program.
If fopen
fails, you print a warning but continue with the program anyway. You should exit here, or otherwise handle the event to prevent future reads from failing.
It would also be prudent to print errors to the standard error stream.
if ((input = fopen(argv[1], "r")) == NULL)
{
/* printf("Error, the file %s cannot be opened\n", argv[1]); */
fprintf(sterr, "Failed to open [%s].\n", argv[1]);
return EXIT_FAILURE;
}
Casting the result of malloc
(or calloc
, etc.) is not necessary in C. A void *
can be safely and implicitly promoted to any other pointer type.
In addition to this, sizeof (char)
is guaranteed to be 1
.
/* word = (char *)malloc(sizeof(char) * W); */
word = malloc(W);
In sscanf(inBuf, "%s", &words)
, &words
is a char (*)[SIZE]
, which is the wrong type for %s
.
The &
is not needed. words
is of type char [SIZE]
, which will decay to the appropriate char *
type when passed to a function.
fgets
, fscanf
, and sscanf
can all fail. You should guard against this to prevent the rest of your program working with incorrect data.
fgets
returns NULL
on failure. *scanf
functions return the number of successful conversions performed ("%s"
is one conversion, "%i%i%i"
is three).
if (NULL == fgets(inBuf, SIZE, input))
/* handle this event */;
if (1 != sscanf(inBuf, "%s", words))
/* handle this event */;
*ptr = *words;
does not perform a string copy. Actually, it copies the first element of words
to the first element of ptr
.
ptr = words
would assign the pointer value.
=
is never used for string copying, and instead, copying is done with the strcpy
function:
char one[64];
char two[] = "hello world."
strcpy(one, two);
Care must be taken to ensure that the destination buffer has enough space for the to-be-copied string (including its null terminating byte).
crossword[y][z]
is a char
. Use %c
not %s
to print single characters.
printf(/* "%s" */ "%c", crossword[y][z]);
The scanf
specifier %s
skips leading whitespace, begins reading characters into its buffer, and stops when it encounters more whitespace.
The result of sscanf(" e aa", "%s", buffer)
would have buffer
be equivalent to "e"
. You will not be able to parse the lines with spaces in them this way.
The character set conversion specifier (%[]
) can be used to overcome this limitation, as it allows reading whitespace. %[^\n]
will read all characters until it encounters a newline.
Note that using either %s
or %[]
without a limiting field width specifier is unsafe as gets
, as you can overflow the buffer.
char buffer[16];
if (1 == sscanf(" e aa\n", "[^\n]", buffer))
printf("[%s]", buffer); /* => [ e aa] */
With that said, the simple solution is to just remove the newline from the input buffer (replace it with the null terminating byte), if it exists, and use that buffer directly.
char input[] = " e aa\n";
input[strcspn(input, "\n")] = 0;
The elements of crossword
are initialized as
crossword[f] = (char *)malloc(N * sizeof(char));
with N
as the length of the second dimension. They are then accessed as
for (int z = 0; z < M; z ) {
/* ... */
crossword[y][z] = token[y][z];
where z
relies on a different bound (M
instead of N
). If N
is 5
, and M
is 6
, this will access memory out of bounds.
In general, there is no need for all these different, intermediate buffers (and pointers to buffers). This can be done with a single buffer for reading each line.
Here is a working example with some helper functions to reduce code repetition. The bulk of the program is in the run
function. It should be relatively straightforward to follow along:
- read the header information
- allocate enough space for our word_count pointers to strings.
- read word_count lines, for each:
- allocate enough memory for it
- copy the string to this allocated memory
- read word_count lines, for each:
- allocate enough memory for a rows * cols matrix
- read rows lines, for each:
- copy each byte from the string (excluding the null byte) to the correct row and column in the matrix
- read rows lines, for each:
- use the data (display it)
- free all allocations
Note that the input file must contain trailing whitespace on lines that require padding up to the specified cols
length (truncation occurs if oversized).
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* output formatted message to stderr and exit program unsuccessfully */
static void panic(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
fputc('\n', stderr);
va_end(args);
exit(EXIT_FAILURE);
}
/* read a line of input into a size limited buffer; removes newline character */
static size_t read_file_line(char *buffer, int size, FILE *file) {
if (!fgets(buffer, size, file))
panic("Could not read user input.");
size_t length = strcspn(buffer, "\n");
buffer[length] = 0;
return length;
}
/* panic unless memory allocation successful */
static void *palloc(size_t bytes) {
void *p = malloc(bytes);
if (!p)
panic("Could not allocate %zu bytes.", bytes);
return p;
}
static void print_matrix(size_t r, size_t c, char (*m)[r][c]) {
putchar(' ');
for (size_t i = 0; i < c; i )
putchar('-');
puts(" ");
for (size_t i = 0; i < r; i ) {
putchar('|');
for (size_t j = 0; j < c; j )
putchar((*m)[i][j]);
puts("|");
}
putchar(' ');
for (size_t i = 0; i < c; i )
putchar('-');
puts(" ");
}
static void run(FILE *file) {
size_t word_count;
size_t rows;
size_t cols;
char input[128];
/* read our header information */
read_file_line(input, sizeof input, file);
if (3 != sscanf(input, "%zu%zu%zu", &word_count, &rows, &cols))
panic("Could not read file header.");
/* allocate the word list, read and allocate each word */
char **words = palloc(sizeof *words * word_count);
for (size_t i = 0; i < word_count; i ) {
size_t len = read_file_line(input, sizeof input, file);
words[i] = palloc(len 1);
strcpy(words[i], input);
}
/* allocate and read our matrix */
char (*matrix)[rows][cols] = palloc(sizeof *matrix);
for (size_t i = 0; i < rows; i ) {
size_t len = read_file_line(input, sizeof input, file);
if (len < cols)
panic("Insufficient column data: Required %zu, Read %zu", cols, len);
for (size_t j = 0; j < cols; j )
(*matrix)[i][j] = input[j];
}
/* display our input */
for (size_t i = 0; i < word_count; i )
printf("WORD: %s\n", words[i]);
print_matrix(rows, cols, matrix);
/* free the memory */
for (size_t i = 0; i < word_count; i )
free(words[i]);
free(words);
free(matrix);
}
int main(int argc, char **argv) {
if (argc != 2)
panic("usage: %s FILENAME", argv[0]);
FILE *file = fopen(argv[1], "r");
if (!file)
panic("Could not open file [%s].", argv[1]);
run(file);
fclose(file);
}
stdout
:
WORD: nail
WORD: taco
WORD: name
WORD: men
WORD: next
WORD: can
------
| next |
|namect|
| e aa|
| n nc|
| nail |
------