1

Hi I wrote this code and it worked but in the end, "the program has stopped working"

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

void main() 
{
    char *s;
    s=(char*)malloc(sizeof(char));
    printf("Enter a string:\n");
    gets(s);
    while (*s)
    {
        if (*s>= 65 && *s<=90)
            printf("%c",*s+32);
        else if(*s>=97 && *s<=122)
            printf("%c",*s-32);
        else
            printf("%c",*s);
        *s++;
    }
    free(s);
}
WedaPashi
  • 3,002
  • 21
  • 38
  • What input did you give? – Panos Antoniadis Apr 20 '18 at 08:42
  • 1
    As a side note: Never ever use gets() in a serious program. It is dangerous due to overflow attacks without size checking. Use fgets() instead. – BluesSolo Apr 20 '18 at 08:42
  • Other side notes: format your code correctly, e.g. like the samples in your C text book. `void main()` is non standard. You cannot call `free(s);` because `s` has changed. You only can free pointers that have been returned by `malloc` and similar functions. – Jabberwocky Apr 20 '18 at 08:43
  • When I remove the "free(s)" it works perfectly. Why? – Hosein Shendabadi Apr 20 '18 at 08:50
  • 1
    You only allocate 1 byte, while you will need at least 2 to write one character (character and null terminator) and more if you actually want it to contain a whole string. And as already mentioned you'll also need to call `free` using the original pointer you received by `malloc` and since you updated `s` you can't use that. – Elijan9 Apr 20 '18 at 08:53
  • This shows the problems in sequence : [writing to too small a buffer](https://taas.trust-in-soft.com/tsnippet/t/c6862144), [freeing a pointer that is not exactly what came from malloc](https://taas.trust-in-soft.com/tsnippet/t/f5e18109), [fixed version](https://taas.trust-in-soft.com/tsnippet/t/3c91b701) – Pascal Cuoq Apr 20 '18 at 09:07
  • "_When I remove the "free(s)" it **works perfectly**. Why?_" No it doesn't, it just hasn't crashed. – TripeHound Apr 20 '18 at 09:19

3 Answers3

4

That code does not work, in fact it has undefined behavior.

This:

s = (char *) malloc(sizeof(char));

allocates 1 byte of storage, into which you then scan a string, thus very likely leading to buffer overflow. The buffer can only hold a single string, i.e. string of 0 characters before the terminator character at the end.

You meant:

s = malloc(128);

or something like that.

There's no need to cast, and sizeof (char) is always 1 so that doesn't add anything.

Also, as more of a code review, magic numbers in code is generally considered a bad idea, instead write:

if (*s >= 'A' && *s <= 'Z')

or even better

if (isupper((unsigned int) *s))

to not hard-code a depdency on ASCII.

UPDATE Oh, and as pointed out in a comment, you can't change the value of s and then pass the changed value to free(), that is undefined behavior also. The address passed to free() must be the same as the one you got back from malloc().

Use a separate variable for the iteration:

char *p = s;

and only free() the original s.

unwind
  • 364,555
  • 61
  • 449
  • 578
  • 1
    Note that statement `*s++` changes the `s` and doing `free(s)` may creates the problem. – Achal Apr 20 '18 at 08:59
  • Very good point on emphasise that isupper and alike are not ASCII dependant! – Jean-Baptiste Yunès Apr 20 '18 at 09:19
  • `isupper` takes an `int`, so I am not sure of the purpose of the cast in `isupper((unsigned int) *s)`. If you mention `isupper`, you can suggest `tolower` in the same snippet, keeping the original control flow. – Pascal Cuoq Apr 20 '18 at 09:36
  • @PascalCuoq It's because the man page says "These functions check whether c, which must have the value of an unsigned char or EOF, falls into a certain character class according to the current locale. ", this cast usually works to avoid warnings. – unwind Apr 20 '18 at 10:09
1

Firstly, by s=(char*)malloc(sizeof(char)); you are allocating only 1 byte of memory for buffer. Allocate enough memory to store the input. Also avoid typecasting malloc() result. Better version looks like

s = malloc(MAX * sizeof(*s));/* MAX is num of bytes you need to define */

Secondly don't use gets() use fgets() instead. Read man 3 gets or check https://linux.die.net/man/3/gets

Finally use int main(void) { } instead of just main(){ }

And more importately when you do free(s) at that time s doesn't point to memory which was earlier allocated to it because of s++ so it may result error like

free(): invalid pointer:

So don't change s use s[row] while iterating OR you can assign s to other pointer and then you can do free(s).

Complete code

int main() {
        char *s = NULL;
        int size = MAX*sizeof(*s);/*define MAX value, it is no of bytes need*/
        s = malloc(size);/* this is generic
                            sizeof(*s) works for any data type */
        printf("Enter a string:\n");
        fgets(s,size,stdin);/* use fgets() instead of gets() */
        int row = 0;
        while (s[row]!='\0') {
                if ( *(s+row) >= 65 && *(s+row) <= 90)
                        printf("%c",*(s+row) + 32);
                else if( *(s+row) >=97 && *(s+row) <= 122)
                        printf("%c",*(s+row) - 32);
                else
                        printf("%c",*(s+row));
                row++;
        }
        free(s);/* s still points to same location */
        return 0;
}

Also you can use isupper() instead of comparing each char ASCII value.

Achal
  • 11,629
  • 2
  • 14
  • 34
  • The “has stopped working” error message in the question makes it sound like the OP may not be using a Unix variant and not have the `man` command, but you can just link to https://linux.die.net/man/3/gets – Pascal Cuoq Apr 20 '18 at 09:18
0

This is wrong.

s = (char*)malloc(sizeof(char));
printf("Enter a string:\n");
gets(s);

s = (char*)malloc(sizeof(char)); allocates 1 byte of memory. And then with gets(s); you get a string, which will be Undefined Behavior.

You have to changed it to

s = malloc(MAX_LENGTH * sizeof(char));   //MAX_LENGTH is user defined

Additionally, you must check if malloc() returned anything. If it returns NULL then it means no memory is allocated and all of the existing program will invoke undefined behavior. Also, there is no need to cast the malloc result, so to further improve your code, you need to change it to,

s = malloc(MAX_LENGTH * sizeof(char));
if(s == NULL)
{
    // Add error handling here
}

Also,

void main()

isn't by the standard anymore, see This post which explains why. If you want to know what C11 standard states about it, then refer the standard here: Section 5.1.2.2.1 Program startup

So change it to,

int main(void)

You should make sure that you call free(s); only if it was allocated. As one of the comments below rightly indicates that free(NULL); is NOT a problem, but it also have no effect, so why call it anyway.

Make s point to NULL again, but its irrelevant in this piece of code.

WedaPashi
  • 3,002
  • 21
  • 38
  • You don't have to make sure that you call `free(s)` only if it was allocated. You only need to make sure that you call it on the result of `malloc()`. If `s` receives the result of `malloc` and `malloc` failed to allocate by returning a null pointer, then you can call `free(s)` because `free(NULL)` is defined and does nothing. – Pascal Cuoq Apr 20 '18 at 09:20
  • @PascalCuoq: Indeed, but I always felt `i = i;` would do nothing better than wasting CPU cycles, whereas I am certain that `free(NULL);` would be no different? But, yes your point is valid. – WedaPashi Apr 20 '18 at 09:39