Can anyone tell me what I did wrong?
Yes. Various problems.
string = (char*)malloc(argc * sizeof(char)); allocates based on the number of arguments (each a string) to the program and not to the lengths of the strings involved. To find the length of a string use size_t len = strlen(s); Add 1 for the string size. @David C. Rankin
i in strcpy(string, argv[i]) is the same value as argc and then argv[argc] == NULL. strcpy(string, argv[i]) is bad, undefined behavior, as argv[i] does not point to a string.
string[i] >= 'a' && string[i] <= 'z') is a weak way to detect uppercase characters. Use isupper() for performance and portability.
string[i] = string[i] - 32; is a weak way to convert to upper case. Use toupper() for performance and portability and then isupper() is not needed.
string = (char*)malloc(argc * sizeof(char)); uses an unnecessary cast. Easier to code right, review and maintain code to size to the de-referenced pointer than to the pointer type. string = malloc(sizeof *string * n);
A classic approach would allocate an array of pointers to strings and then memory for each string. Then convert those to uppercase using standard library functions.
// Get memory for the string pointers
char **uppercase_string = malloc(sizeof *uppercase_string * argc);
if (uppercase_string == NULL) {
fprintf(stderr, "Out of memory");
return EXIT_FAILURE;
}
// Get memory for each string and convert
for (int a=0; a< argv; a++) {
size_t length = strlen(argv[a]);
size_t size = length + 1; // strings always have a final null character.
uppercase_string[a] = malloc(sizeof *uppercase_string[a] * size);
if (uppercase_string[a] == NULL) {
fprintf(stderr, "Out of memory");
return EXIT_FAILURE;
}
for (size_t i = 0; i < length; i++) {
uppercase_string[a][i] = toupper((unsigned char) argv[a][i]);
}
uppercase_string[a][length] = '\0'; // append null character.
}
// Use uppercase_string somehow
for (int a=0; a< argv; a++) {
printf("%d <%s>\n", a, uppercase_string[a]);
}
// free resources when done
for (int a=0; a< argv; a++) {
free(uppercase_string[a]));
}
free(uppercase_string);
[Advanced]
C allows the strings pointed to by argv[i] to be modified.
"The parameters argc and argv and the strings pointed to by the argv array shall be modifiable by the program, and retain their last-stored values between program startup and program termination." C11dr §5.1.2.2.1 2
Although I consider it weak practice to mod argv[], code could forego the memory allocation.
for (int a= 1; a < argc; a++) { // start at 0 it you want to convert the program name too.
char *string = argv[a];
for (size_t i = 0; string[i]; i++) {
if(string[i] >= 'a' && string[i] <= 'z') {
string[i] = string[i] - 32;
}
}
}