First, you need to learn to count, there are 58-characters
in your sentence, and second, you are making this much harder on yourself than it needs to be...
If I understand your question, you want to:
- read a sentence entered by the user;
- determine the length;
- modify the sentence and replace all
spaces
with '_'
(underscores);
- form a square array of minimum sufficient size to hold the characters in the sentence (e.g. 58-chars, requires 8x8 array);
- fill the square array with the modified sentence and fill all empty elements of the array after the last character of the sentence with underscores; and
- shift each row in the array left by 1-character moving the 1st character to the place previously occupied by the last.
You have already included the necessary headers that provide memset
memcpy
and memmove
-- which will greatly simplify your task.
To begin, as is mentioned in the comments never, never, ever use gets
. It is so insecure and so prone to exploit by buffer overrun, it has been completely removed from the C11 standard library. Use fgets
instead, e.g.:
#define MAXS 1024 /* if you need a constant - define one (or more) */
...
char sentence [MAXS] = "";
size_t i, len = 0, size;
fputs ("Please enter a sentence to be encrypted:\n", stdout);
if (!fgets (sentence, MAXS, stdin)) { /* read/validate sentence */
fputs ("error: user canceled input or EOF\n", stderr);
return 1;
}
Next when using fgets
, it will read up to (and including) the trailing newline ('\n'
) generated by the user pressing Enter. You need to remove the trailing '\n'
from your sentence. You can do this with a simple check of the character at length - 1
. If it is a '\n'
, simply overwrite it with the nul-character ('\0'
or simply the equivalent - 0
).
If the last character is not '\n'
, then you need to check if the length of the string (-1
) is the maximum your string can hold. If you have stored the maximum number of characters, and the final character isn't '\n'
-- that indicates that the user entered more characters than your buffer can hold -- meaning that there are characters in stdin
that remain unread -- handle accordingly, e.g.
len = strlen (sentence); /* get sentence length */
if (len && sentence[len - 1] == '\n') /* is last char '\n' ? */
sentence[--len] = 0; /* overwrite with nul-char */
else if (len == MAXS - 1) { /* otherwise - input exceeds MAXS chars */
fprintf (stderr, "error: string exceeds %d chars.\n", MAXS);
return 1;
}
How would you determine the needed size of your square array from the number of characters (len
) of your sentence? The square-root of the length (as an integer value) + 1 is pretty simple:
size = (size_t)sqrt(len); /* set size (trucation intentional) */
if (len % size)
size += 1;
You may as well complete the modifications to your sentence at this point, replacing all spaces
with '_'
, e.g.
for (i = 0; i < len; i++) /* replace ' ' with '_' */
if (sentence[i] == ' ')
sentence[i] = '_';
Now all you need to do is declare block
as a 2D VLA (if you don't have the VLA extension, you can declare a pointer-to-pointer-to-char and allocate size
number of pointers and assign an allocated block of size
chars to each pointer using malloc
). Since your original code makes use of a VLA, we will go that route.
After declaring your VLA, all you need to do to fill it with your modified sentence assuring all other unused elements are underscores is to memset
the array to all '_'
before you memcpy
your modified sentence to the array, e.g.:
char block[size][size]; /* declare VLA */
memset (block, '_', size * size); /* set all to '_' */
memcpy (block, sentence, len); /* copy sentence to block */
That's it. All that remains is shifting each row to the left by 1-character. This can be accomplished by simply saving the first character in each row in a temporary variable and then using memmove
to shift each character in the row left by 1, and then set the last character in the row to the temp char you saved. (you should use memmove
instead of memcpy
because the source and destination memory overlaps), e.g.
/* shift the array left */
for (i = 0; i < size; i++) {
char tmp = *block[i]; /* save 1st char in row */
memmove (block[i], &block[i][1], size - 1); /* shift row left by 1 */
block[i][size - 1] = tmp; /* put 1st char as last */
}
That is all you need to do to accomplish what you state in your question if I understood you correctly. Putting it altogether, you could do something similar to:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#define MAXS 1024 /* if you need a constant - define one (or more) */
void printarray (int size, char block [size][size])
{
int row, col;
for (row = 0; row < size; row++) {
for (col = 0; col < size; col++)
putchar (block[row][col]); /* don't use printf to output */
putchar ('\n'); /* a single-character */
}
}
int main (void) {
/* sentence is:
* "Patience is a minor form of despair disguised as a virtue."
*/
char sentence [MAXS] = "";
size_t i, len = 0, size;
fputs ("Please enter a sentence to be encrypted:\n", stdout);
if (!fgets (sentence, MAXS, stdin)) { /* read/validate sentence */
fputs ("error: user canceled input or EOF\n", stderr);
return 1;
}
len = strlen (sentence); /* get sentence length */
if (len && sentence[len - 1] == '\n') /* is last char '\n' ? */
sentence[--len] = 0; /* overwrite with nul-char */
else if (len == MAXS - 1) { /* otherwise - input exceeds MAXS chars */
fprintf (stderr, "error: string exceeds %d chars.\n", MAXS);
return 1;
}
printf ("\nsentence: '%s'\n\n", sentence); /* output sentence */
size = (size_t)sqrt(len); /* set size (trucation intentional) */
if (len % size)
size += 1;
printf("The number of chars is %zu and the size is %zu\n\n", len, size);
for (i = 0; i < len; i++) /* replace ' ' with '_' */
if (sentence[i] == ' ')
sentence[i] = '_';
char block[size][size]; /* declare VLA */
memset (block, '_', size * size); /* set all to '_' */
memcpy (block, sentence, len); /* copy sentence to block */
printf ("block array:\n\n"); /* output original block array */
printarray (size, block);
/* shift the array left */
for (i = 0; i < size; i++) {
char tmp = *block[i]; /* save 1st char in row */
memmove (block[i], &block[i][1], size - 1); /* shift row left by 1 */
block[i][size - 1] = tmp; /* put 1st char as last */
}
printf ("\n\nshifted block array:\n\n");
printarray (size, block);
return 0;
}
(note: if you are using windoze, replace all %zu
format specifiers with %lu
as windows does not provide the z
modifier for size_t
)
Example Use/Output
$ ./bin/array_block_shift
Please enter a sentence to be encrypted:
Patience is a minor form of despair disguised as a virtue.
sentence: 'Patience is a minor form of despair disguised as a virtue.'
The number of chars is 58 and the size is 8
block array:
Patience
_is_a_mi
nor_form
_of_desp
air_disg
uised_as
_a_virtu
e.______
shifted block array:
atienceP
is_a_mi_
or_formn
of_desp_
ir_disga
ised_asu
a_virtu_
.______e
memxxx
Function Equivalents
Given your comments and questions regarding the memxxx
functions, and the fact you are self-teaching, you may benefit from stepping through this equivalent re-write of the code using only simple loops instead of memset
, memcpy
and memmove
. Though you are always better off using a tried and true library function, there is great learning value in peeling back the covers and looking at what they do inside.
One note. memcpy
and memmove
do effectively the same thing, but memcpy
is only defined where the source
and destination
of the copy do not overlap. memmove
is safe for regions that do overlap. So in your case where we copy elements 1, 2, 3 ... 8
left by one to 0, 1, 2 ... 7
use of memmove
was required because the regions overlap.
Advice -- Always Compile with Warnings Enabled -- Use the man pages
Also, one final note before the example, always compile with warnings enabled, and do not accept code until it compiles without warning. The minimum warnings you should use are -Wall -Wextra
(and you should also add -pedantic
for a few additional warnings). I would also recommend -Wshadow
to catch any unintentional shadowed variables that may cause problems.
You should compile with these options as part of you compile string every time you compile. If you are on windows using VS (cl.exe
), then at minimum use /W3
(you can disable specific warnings on windows with /wdXXXX
where XXXX
is the warning code you wish to disable)
Read and understand each warning. Yes, take the time to read and understand each one. The provide a concise description of the problem and provide the line number on which the problem occurs. Go address each one. You can learn just as much C just by listening to what the compiler is telling you as you can from most tutorials.
(gcc/clang warnings are far superior to those provided by VS, and with VS if you enable /Wall
you will get a number of general compiler related warnings that are non-code specific warnings, which with a 1/2 dozen /wdxxxx
can be reduced to just what is relevant to your code)
As mentioned in the comments below, if you are on Linux/Unix, the man pages
give detailed usage information on every C-library function. Use them. Just open a terminal and, type e.g man memmove
to learn about the memmove
function. When you are learning (and even after you think you have learned a function) always consult man functionname
if you have any question about how it is used or the proper parameters it takes and most importantly what values it returns on success and what values it returns to indicate failure (and whether additional information is set in errno
which can be retrieved with perror()
-- print error). If you just incorporate these basic tools that are available for you to use, you will reduce the frustration for learning 10-fold, and will be light-years ahead of your classmates in no time...
On to the example, that does exactly what the code above does, but eliminating all reliance on memxxx
(which will let you see why they can simplify your life)
#include <stdio.h>
#include <string.h>
#define MAXS 1024 /* if you need a constant - define one (or more) */
void printarray (int size, char block [size][size])
{
int row, col;
for (row = 0; row < size; row++) {
for (col = 0; col < size; col++)
putchar (block[row][col]); /* don't use printf to output */
putchar ('\n'); /* a single-character */
}
}
int main (void) {
/* sentence is:
* "Patience is a minor form of despair disguised as a virtue."
*/
char sentence [MAXS] = "";
size_t i, j, len = 0, size;
fputs ("Please enter a sentence to be encrypted:\n", stdout);
if (!fgets (sentence, MAXS, stdin)) { /* read/validate sentence */
fputs ("error: user canceled input or EOF\n", stderr);
return 1;
}
len = strlen (sentence); /* get sentence length */
if (len && sentence[len - 1] == '\n') /* is last char '\n' ? */
sentence[--len] = 0; /* overwrite with nul-char */
else if (len == MAXS - 1) { /* otherwise - input exceeds MAXS chars */
fprintf (stderr, "error: string exceeds %d chars.\n", MAXS);
return 1;
}
printf ("\nsentence: '%s'\n\n", sentence); /* output sentence */
if (len < 4) {
fputs ("error: sentence less than 4 chars - too short.\n", stderr);
return 1;
}
for (size = 2; size * size < len; size++) {} /* set size */
printf("The number of chars is %zu and the size is %zu\n\n", len, size);
for (i = 0; i < len; i++) /* replace ' ' with '_' */
if (sentence[i] == ' ')
sentence[i] = '_';
char block[size][size]; /* declare VLA */
for (i = 0; i < size; i++) /* initialize all element '_' */
for (j = 0; j < size; j++) /* (memset (block, '_', size) */
block[i][j] = '_';
size_t n = 0;
for (i = 0; i < size; i++) /* copy sentence to block */
for (j = 0; j < size; j++) /* (memcpy (block, sentence, len) */
if (n < len)
block[i][j] = sentence[n++];
else
break;
printf ("block array:\n\n"); /* output original block array */
printarray (size, block);
/* shift the array left (memmove (block[i], &block[i][1], size - 1)) */
for (i = 0; i < size; i++) {
char tmp = block[i][0]; /* save 1st char in row */
for (j = 1; j < size; j++)
block[i][j-1] = block[i][j];
block[i][size - 1] = tmp; /* put 1st char as last */
}
printf ("\n\nshifted block array:\n\n");
printarray (size, block);
return 0;
}
note: to more closely model what memcpy (block, sentence, len)
actually does, you could use:
for (i = 0; i < len; i++) /* copy sentence to block */
(*block)[i] = sentence[i]; /* (memcpy (block, sentence, len) */
but note, this would only apply to 2D arrays where len
is guaranteed to be less than size * size
. Further, it would not apply to char **
where you allocate for size
pointers of size
characters each. Why? Only an array guarantees that all elements will be sequential in memory. There is no such guarantee when using a collection of pointers and independently allocated blocks of memory to simulate a 2D array.
The use/output is exactly the same.
Look things over and let me know if I misunderstood your goals, or if you have further questions.