views:

1106

answers:

5

I want to write a function that reverses the given string passed into it. But, I can not. If I supply the doReverse function (see code below) with a character array, my code works well.

I can't figure out why this does not work. I am able to access str[0] in doReverse, but I can't change any value of the array by using a char pointer. Any ideas?

void doReverse(char *str) {
    str[0] = 'b';
}

void main(void) {
    char *str = "abc";
    doReverse(str);
    puts(str);
}

Update:

I know how to do write a reverse function by passing a character array to it:

void reverse1(char p[]) {
    int i, temp, y;

    for (i = 0, y = strlen(p); i < y; ++i, --y) {
        temp = p[y-1];
        p[y-1] = p[i];
        p[i] = temp;
    }
}

But, I want to write another version that gets a char pointer as a parameter.

+6  A: 

Because this is homework I'll give advice but I won't post a complete solution.

I'm guessing you're getting an access violation on str[0] = 'b'? This is because "abc" is a string literal.

Copy the string str points to before calling reverse, or get reverse to allocate a buffer and put the reversed string into that.

Keep in mind you'll have to deallocate all the memory you allocate.

Binary Worrier
+1  A: 

As far as I know, constant strings are implemented as arrays of constant characters (or, in C terms, const char [length]). Therefore, you can't modify its characters.

Try dynamically allocating the string.

char* str = (char*)malloc(sizeof(char) * 4);
strcpy(str, "abc");
str[3] = '\0';

Of course, don't forget to free the memory at the end of your program.


Edit: I won't post anything related to reversing the string, because that's your work.

Eduardo León
thanks, Eduardo.This is what i was looking for and your solution helped me.
Or, if you want to avoid the uneeded malloc, just create an array on the stack and have the compiler copy the string: char str[] = "abc";
Eclipse
This code fragment contains a lot of cruft, viz. the casting of malloc, the "sizeof(char)", and the explicit setting of str[3].
Cirno de Bergerac
+8  A: 

The simplest solution is to change the declaration of str to

char str[] = "abc";

This makes str an array of char that is initialized to the string "abc". Currently you have str as a pointer-to-char initialized to point to a string described by a string literal. There is a key difference: string literals are read-only to give the compiler maximum flexibility on where to store them; it is UB to modify them. But arrays of char are mutable, and so it's okay to modify those.

PS. main() returns an int.

Cirno de Bergerac
PPS: `int main()` does not have to return anything explicitly. In C all functions return an int, and `void` will return `0`, thus making it legal to do `void main()` in C. So I guess because of this they have allowed main functions in C++ to omit the return statement and assume it as `0`
Marius
+3  A: 

Since you're studying for an exam, I'll flesh out my comments a bit to explain what's actually happening:

char *str = "abc";

str is a pointer stored on the stack. It gets initialized to point to the literal string "abc". That literal string is going to be stored in the data section of your compiled executable and gets loaded into memory when your program is loaded. That section of memory is read-only, so when you try and modify the data pointed to by str, you get an access violation.

char* str = malloc(sizeof(char) * 4);
strcpy(str, "abc");

Here, str is the same stack pointer as the first example. This time, it is initialized to point to a 4-character block of memory on the heap that you can both read and write. At first that block of memory is uninitialized and can contain anything. strcpy reads the block of read-only memory where "abc" is stored, and copies it into the block of read-write memory that str points to. Note that setting str[3] = '\0' is redundant, since strcpy does that already.

As an aside, if you are working in visual studio, use strcpy_s instead to make sure you don't overwrite your buffer if the string being copied is longer than you expected.

char str[] = "abc";

Here str is now an array allocated on the stack. The compiler will sized it exactly fit the string literal used to initialize it (including the NULL terminator). The stack memory is read-write so you can modify the values in the array however you want.

char str[4] = "abc";

This is effectively the same as the previous version, only you are telling the compiler that you know better than it does how long the array should be. You may get into trouble if you change the string and not the array size.

Eclipse
Casting the result of malloc() is never a good idea. In C, it works without the cast (unless you didn't #include <stdlib.h>, in which case the nice compiler will tell you if you leave out the cast), and in C++ you shouldn't use malloc().
David Thornley
Right - I was just explaining Eduardo's answer above.
Eclipse
Using strcpy() itself is indeed not a good idea, but strncpy() prevents buffer overwrites while being standard C.
David Thornley
Right, Josh, but it looked to me like you were being insufficiently critical for my taste. I also thought I'd put in a plug for the "strn" functions.
David Thornley
Fair enough - my beef with the strn functions is that they don't always write the trailing NULL
Eclipse
@David Thornley: `strncpy()` isn't really that safe either. If there's no room for a terminating null character, then one isn't put there! Meaning, you have to specify one less than the size of the buffer and terminate manually just to be sure.
dreamlax
+1  A: 

Lordy, Lordy. To all those suggesting ways to actually perform the exchange, please read the question carefully; there's nothing worse than having to reiterate an already perfectly articulate question. Irrespective of the method used to implement the exchange (temp-swap, xor3-swap, etc), this person appears to be all too familiar with the function's fundamental and rather elementary intrinsics.

However, as already explained, the compiler/linker generically places all string literals within the 'const data-segment' of the target executable, which is subsequently associated with a non-writeable MMU descriptor during the appropriate 'load/exec' invocation. All CPU write-cycles subsequently issued via this descriptor are automatically trapped by the MMU's exception mechanism, resulting in the obligatory 'segfault' or platform-specific equivalent. It goes without saying of course, older non-MMU platforms would exhibit no such behaviour.

Although this effectively procures run-time support for the source language's 'constant/literal' idiom, several platforms have historically facilitated explicit compile-time segment overrides. However, this level of support has slowly diminished in favour of a more rigid/robust abstraction layer, thus rendering many obvious and often useful optimisations untenable. As time and attrition yields its steadily ageing "MC/ASM" philosophy before an all too eager "Microsoft" generation, programmers are no longer deemed knowledgeable or responsible enough to make this kind of decision. In lieu of the many contrived, not to say creative, implementations I have witnessed as a project leader, this is by no means a bad thing.

Although this post is rapidly evolving into an off-topic violation, I feel somewhat vindicated by the constant stream of top-down related questions which are slowly becoming endemic within our industry. As a fledgling C programmer - a language originally designed to complement low-level development - my advice is to adopt a bottom-up approach and augment your studies with a little extra-curricular assembly language development. As algorithmic implementation is likely to constitute your primary focus as an applications engineer, it is important to remember that contemporary CPU design has experienced a homogeneous evolution over the past 30 years; today's ultra-fast Intel CPUs are no more than super-scalar CMOS refinements of the 4/8-bit bipolar processors I was programming when the Earth was still young.

Contrary to popular belief, assembly language programming is relatively easy to learn, and absolutely essential when attempting to reconcile high-level constructs with problematic or esoteric behaviour. Once you factor in the endless hours of experimentation, debugging, web-searching and forum-spamming, there can be no doubt that a bottom-up approach is certainly the path of least resistance.

Good luck with your studies.