-1

I have to make a program that enters a sentence to be encrypted by shifting the sentence. I have to first transfer the sentence to a 2D array, whose size must be a perfect square which maximizes the spaces needed. (For example if the number of characters in the sentence is 54, the size of the array would be 8 instead of 7, since it fits the whole sentence). The spaces and empty values in the sentence should be replaced by an underscore. Then I need the array to shift left, but it crashes.

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


void printArray (int size, char block [size][size])
{
    int row, col;
    for (row = 0; row < size; row++)
{
    printf("\n");
    for (col = 0; col < size; col++)
    {
        printf("%c", block[row][col]);
    }
}
}

int main ()
{
// Sentence is "Patience is a minor form of despair disguised as a virtue."

char sentence [1000];
printf("Please enter a sentence to be encrypted: ");
gets (sentence);

int numOfChar = strlen(sentence);
int size = 1;
while (size <= (numOfChar/size))
{
    size++;
}
printf("\nThe number of chars is %d and the size is %d", numOfChar, size);


int n = (size * size) - numOfChar;                      // fills the             
empty values with "_"'s
char fillBlank [n];
int i;
for (i = 0; i < n; i++)
{
    fillBlank[i]  = '_';
}
strcat(sentence, fillBlank);

// ------------------------------------------------ Makes the array ------------------------------------------- //

char block [size][size];
int row;
int col;
int counter = 0;

while (counter < (size * size))
{
    for (row = 0; row < size; row++)
    {
        for (col = 0; col < size; col++)
        {
            if (sentence [counter] == ' ')
                block [row][col] = '_';
            else if (sentence[counter] == '\0')
                block [row][col] = '_';
            else
                block [row][col] = sentence [counter];
            counter++;
        }
    }
}

// ------------------------------------------- Prints the array --------------------------------------------- //

printArray(size, block [size][size]);
/*
for (row = 0; row < size; row++)
{
    printf("\n");
    for (col = 0; col < size; col++)
    {
        printf("%c", block[row][col]);
    }
}
*/
    // ------------------------------------------- Shifts the array left --------------------------------------- //

printf("\n\n\n");
char temp [size];
col = 0;
for (row = 0; row < size; row++)
{
    temp [row] = block [row][col];
}

for (row = 0; row < size; row++)
{
    for (col = 0; col < size; col++)
    {
        block [row][col] = block [row][col+1];
    }
}

col = 7;
for (row = 0; row < size; row++)
{
    block [row][col] = temp [row];
}

printArray(size, block [size][size]);
/*
for (row = 0; row < size; row++)
{
    printf("\n");
    for (col = 0; col < size; col++)
    {
        printf("%c", block[row][col]);
    }
}
*/
return 0;
}
  • 1
    `block [row][col+1];` this accesses outside the bounds of your array when col = size-1. Edit your post to define what you mean by *Then I need the array to shift left*. It's hard to understand what your intended output is. Also [don't call `gets`, use `fgets` instead](https://stackoverflow.com/questions/1694036/why-is-the-gets-function-so-dangerous-that-it-should-not-be-used) – MFisherKDX May 02 '18 at 03:35

1 Answers1

1

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:

  1. read a sentence entered by the user;
  2. determine the length;
  3. modify the sentence and replace all spaces with '_' (underscores);
  4. form a square array of minimum sufficient size to hold the characters in the sentence (e.g. 58-chars, requires 8x8 array);
  5. 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
  6. 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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
  • Thank you soo much for the help. It's just that I have a supply teacher who has no prior knowledge to comp sci, and I have her for the whole sem. So I'm self teaching this and I'm pretty new. So I dont know much about pointers except the basics. But I'm guessing *block [i] is the same as block [i][]. I'm also a little confused at what size_t is and the if statement that checks if the last value of the string is a '\n'. Also I need clarification on the mem functions, as I've never used or heard any of these before. – Kevin Kannammalil May 02 '18 at 12:25
  • Yes you got it. `*block[i]` is the same as `*(block[i] + 0)` in pointer notation which is simply `block[i][0]` in indexed notation. Remember a pointer is just a normal variable that holds the *address of* something else as its value. `memset` simply sets every byte to the character specified for a block of memory `size` big. `memmove` is just like `strcpy` for memory that overlaps, there is also `memcpy`. Both do the same thing, but since memory isn't terminated by `'\0'`, you have to specify the number of bytes to copy. So I just copy elements `1-size` to `0` and move `0` to `size-1`. – David C. Rankin May 02 '18 at 19:26
  • You can replace the `memxxx` functions with simple loops if you like, but there is less chance of an unintended error using the library functions that have been exhaustively tested. Every function has a `man page`, so any you are unclear on, just open a terminal and type, e.g. `man memmove`. (they are cryptic at first, but they give every relevant detail for every function in the C-library -- so make friends with them...) – David C. Rankin May 02 '18 at 19:29