The strcat function appends a copy of the string pointed to by s2 (including the terminating null character) to the end of the string pointed to by s1. The initial character of s2 overwrites the null character at the end of s1. If copying takes place between objects that overlap, the behavior is undefined.
char *strcat(char *restrict s1, const char *restrict s2) {
memcpy(s1 + strlen(s1), s2, strlen(s2) + 1);
return s1;
}although personally I'd prefer to use strncat() instead and include some checks (especially when the concatenated string does not completely fit in s1, as standard strncat() does not include the terminating null characrer in that case). In general, I prefer the BSD strlcat() semantics:char *strlcat(char *s1, const char *s2, const size_t size) {
const size_t n1 = strnlen(s1, size);
const size_t n2 = strnlen(s2, size);
if (n1 >= size) {
s1[size - 1] = '\0';
} else
if (n1 + n2 > size) {
memcpy(s1 + n1, s2, size - n1 - 1);
s1[size - 1] = '\0';
} else
if (n2 > 0) {
memcpy(s1 + n1, s2, n2);
s1[n1 + n2] = '\0';
}
return s1;
}
STM32 32F417 Cube IDE 1.14.1 GCC C v11
char buffer[1000];
buffer[0]=0;
Now buffer contains a 0x00 and then random data.
strcat(buffer, "\r\n");
this writes just the two chars to buffer, not a \0 as well
strcat(buffer, "\r\n\0");
this does it correctly
char buffer[] = "abcdefghijklmnopqrstuvwxyz";
buffer[0] = 0;
strcat(buffer, "\r\n");
But if the issue is that the code does explicitely writes only the first two bytes and the third is already zero, then it's "normal" behavior for an optimizing compiler.

Spent all day chasing weird issues.QuoteBut if the issue is that the code does explicitely writes only the first two bytes and the third is already zero, then it's "normal" behavior for an optimizing compiler.
I agree but then I would have never spotted it
No; doing strcat \r\n put in just the \r\n (2 bytes) and the next byte was as it was before (\07 in this case).
IanB - I will try that tomorrow, in isolation. Going to bed nowSpent all day chasing weird issues.
The optimisation is -Og i.e. nowhere near the highest.
strcat(buffer, "\r\n");
this writes just the two chars to buffer, not a \0 as well
.LC0:
.ascii "\015\012\000"
foo:
push {lr}
sub sp, sp, #1004
movs r3, #0
strb r3, [sp]
mov r0, sp
bl strlen
add r2, sp, r0
movw r3, #:lower16:.LC0
movt r3, #:upper16:.LC0
ldrh r1, [r3] @ unaligned
ldrb r3, [r3, #2] @ zero_extendqisi2
strh r1, [sp, r0] @ unaligned
strb r3, [r2, #2]
ldrb r0, [sp, #2] @ zero_extendqisi2
add sp, sp, #1004
ldr pc, [sp], #4
li a5,13
sb a5,0(a0)
li a5,10
sb a5,1(a0)
sb zero,2(a0)
You are better off using snprintf instead of strcat because snprintf results in a valid string which does not exceed the provided buffer length by definition. I never use strcat (or strncat) because it can result in an unterminated string (or buffer overflow) which can lead to all sorts of bugs.
// Function returns a pointer to the customer application string.
#include "appname.ini"
char * get_appname(void)
{
return (appnamestring);
}
QuoteYou are better off using snprintf instead of strcat because snprintf results in a valid string which does not exceed the provided buffer length by definition. I never use strcat (or strncat) because it can result in an unterminated string (or buffer overflow) which can lead to all sorts of bugs.
According to above, strcat should always append a \0 so if adding e.g. "\r\n" to a buffer, it should reliably search that buffer until it finds a \0 and then replace that \0 with \r\n\0.
Working one step at a time from a working version, I found that replacing
char appnamestring [] = "appname_1.1";
with
char appnamestring [] = {"appname_1.1\0"};
makes it work. The 2nd one is a more explicit initialisation. The context is:
char buffer[1000];
buffer[0]=0;
strcat(buffer, "\r\n");char buffer[1000];
buffer[0]=0;
strcat(buffer, "\r\n");
for(int i=0; i<3; i++)
printf("%02x ", buffer[i]);
printf("\n");
Working one step at a time from a working version, I found that replacing
char appnamestring [] = "appname_1.1";
with
char appnamestring [] = {"appname_1.1\0"};
makes it work.
void foo() {
const char *name = get_appname();
const size_t namelen = (name) ? strlen(name) : 0;
// Temporary copy on stack
char modified[namelen + 2 + 1]; // +2 for "\r\n", +1 for final "\0"
#if 1
if (namelen > 0)
memcpy(modified, name, namelen);
modified[namelen + 0] = '\r';
modified[namelen + 1] = '\n';
modified[namelen + 2] = '\0';
#else
modified[0] = '\0';
if (namelen > 0)
strcat(modified, name);
strcat(modified, "\r\n");
#endif
//
// Do something with the 'modified' string
//
// Since 'modified' was on stack, it will not exist after we return from this function
}Here, the temporary copy is on stack. The #if 1 ... #else ... #endif shows the two ways to create the copy and append the CR-LF newline, but since it is just two fixed characters, I recommend using the first.
No; those strings are used read-only.
I can recreate the problem reliably just by removing those curly brackets and the \0. I checked in the .map file that the stored string (in RAM) is the same although, as expected, the \0 adds an extra \0 to the end of each one.
QuoteYou are better off using snprintf instead of strcat because snprintf results in a valid string which does not exceed the provided buffer length by definition. I never use strcat (or strncat) because it can result in an unterminated string (or buffer overflow) which can lead to all sorts of bugs.
I don't agree with the above advice because you can get the length argument to snprintf wrong just as easily as you can misallocate the space in the buffer.
Nominal Animal - if I knew the answer then I would have a fix
Sizeof() will give you the correct length for the buffer you provide to snprintf. It is bullet-proof.
No. Sizeof() will give you the correct length for the buffer you provide to snprintf. It is bullet-proof. On top of that, modern GCC compilers will throw a warning if you potentially can put more text into a buffer than will fit.




