Passing 'unsigned short[8]' to parameter of type 'CHAR16 *' (aka 'short *') converts between pointers to integer types with different sign

68 views Asked by At

I have the following code:

//#include "stdint.h"

#include "uefi.h"
//#include "efi_libs.h"

void PrintLn(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable);
void Print(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable);

EFI_STATUS efi_main(EFI_HANDLE ImageHandle, EFI_SYSTEM_TABLE* _SystemTable)
{
    //    SystemTable = _SystemTable;

    _SystemTable->ConOut->Reset(_SystemTable->ConOut, 1);
    _SystemTable->ConIn->Reset(_SystemTable->ConIn, 1);
    _SystemTable->ConOut->SetAttribute(_SystemTable->ConOut, EFI_GREEN);

    Print(L"Welcome", _SystemTable);
    PrintLn(L"Press any key to continue...", _SystemTable);

    EFI_INPUT_KEY key;
    while (_SystemTable->ConIn->ReadKeyStroke(_SystemTable->ConIn, &key) == EFI_NOT_READY);

    return EFI_SUCCESS;
}

void Print(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable)
{
    _SystemTable->ConOut->OutputString(_SystemTable->ConOut, String);
}

void PrintLn(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable)
{
    _SystemTable->ConOut->OutputString(_SystemTable->ConOut, String);
    _SystemTable->ConOut->OutputString(_SystemTable->ConOut, L"\r\n");
}

And I am getting this warning when compiling it:

src/efi_main.c:17:8: warning: passing 'unsigned short[8]' to parameter of type 'CHAR16 *' (aka 'short *') converts between pointers to integer types with different sign [-Wpointer-sign]
        Print(L"Welcome", _SystemTable);
              ^~~~~~~~~~
src/efi_main.c:7:20: note: passing argument to parameter 'String' here
void Print(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable);
                   ^

Now it has been many years since I wrote ANSI C, and I have no idea how to eradicate the warning, can someone please explain the warning to me and show me how t fix it?

2

There are 2 answers

0
CherryDT On

Your Print accepts a pointer to a CHAR16 i.e. short i.e. signed short. However, L"Welcome" produces a pointer to a wchar_t i.e. unsigned short. So you attempt to pass an unsigned short * into a signed short * argument, hence the warning.

The solution is therefore to either change the string-related functions to accept wchar_t * instead of CHAR16 * arguments (which would make sense given that L string literals emit a wchar_t[]) or to change the typedef of CHAR16 to be compatible by making it unsigned instead of signed.

0
John Bollinger On

Now it has been many years since I wrote ANSI C, and I have no idea how to eradicate the warning, can someone please explain the warning to me and show me how t fix it?

Nature of the warning

The warning's descriptive text is:

warning: passing 'unsigned short[8]' to parameter of type 'CHAR16 ' (aka 'short ') converts between pointers to integer types with different sign

That is accompanied by marked-up code excerpts that show exactly where the mismatch appears. In particular, the offending argument is the wide-string literal L"Welcome" in the line ...

    Print(L"Welcome", _SystemTable);`

... and the corresponding parameter is String in ...

void Print(CHAR16* String, EFI_SYSTEM_TABLE* _SystemTable);

.

Wide string literals expressed with the L prefix represent arrays of wchar_t, whose details are implementation defined. Apparently, your implementation defines it as [signed] short int.

CHAR16 is your own thing, apparently defined as or expanding to unsigned short int.

Like any array would be, the wide string literal is automatically converted to a pointer to its first element (type short int *) where it appears in the function call expression. The corresponding parameter has type unsigned short int *. These types differ in the signedness of their pointed-to types, and that's exactly what the warning is telling you.

Significance of the warning

This warning is calling your attention to a comparatively benign situation. The two pointer types involved are "compatible" in the formal sense of the C language spec, and, given the types as defined, reading the elements of the string literal via a pointer of type unsigned short int * is allowed. Technically, there is a potential that such reads might not produce what you expect, but in practice, you are unlikely to be targeting a platform where that could be the case.

Other issues

There are some potentially more significant issues here, however.

  • Most importantly, although [wide] string literals do not technically have elements of const-qualified type, attempts to modify their elements nevertheless produce undefined behavior. On many systems, that usually manifests as a crash. On others, various other types of unwanted behavior can manifest. To avoid risk of unintentionally trying to perform such modifications, pointers to string literal elements should be declared as pointers to const, and const-correctness should be maintained. Example: void consume_wide_string_literal(const wchar_t *wide_string);

  • The wchar_t data type of wide string literal elements is not necessarily a 16-bit type. Although it often is 16 bits, C allows it to be either wider or narrower (consistently for a given C implementation). If you want an integer type that C itself guarantees is exactly 16 bits wide then you may have options:

    • If you can rely on at least C99, or if your compiler provides them as an extension, then int16_t and its unsigned analog uint16_t, both declared in stdint.h, are integer types exactly 16 bits wide, with no padding bits, the former represented in two's complement format, supposing the implementation has any such types at all. However, this does not directly help you with L-style wide string literals if wchar_t is wider or narrower than 16 bits.
    • If you are prepared to rely on C11 or later then you could also choose char16_t (defined in uchar.h) and wide string literals prefixed with u instead of L.

How to fix it

You have multiple options for fixing it, varying in complexity.

  • Don't: these particular warnings may well be highlighting an issue that is strictly hypothetical for you. You have the option of just accepting that the compiler will report them. This has the advantage that if you ever decide that you want or need to really fix them then the compiler will still be telling you where to look.

  • Disable the warning in question. From the form of the diagnostic, you appear to be compiling with GCC or with a compiler that emulates its diagnostics and options. I anticipate that if you add the option -Wno-pointer-sign to your compiler command line then it will stop reporting any diagnostics in this category. That has the advantage that it will genuinely silence the warnings without requiring code changes, and the disadvantage that it may also silence other warnings in the same category that you actually wanted to see.

  • Cast: cast the wide string literal to the type of the function parameter:

        Print((CHAR16 *) L"Welcome", _SystemTable);
    

    That should silence the warnings, but if they are relevant to a real underlying problem then this just masks the issue. It doesn't actually fix anything.

  • Create pseudo-literals of type CHAR16[], and use those. For example:

        static const union {
            wchar_t as_wchar[sizeof(L"Welcome") / sizeof(wchar_t)];
            CHAR16  as_char16[sizeof(L"Welcome") / sizeof(wchar_t)];
        } welcome = { L"Welcome" };
    
        Print(welcome.as_char16, _SystemTable);
    

    If you really wanted to pursue this approach, then you might also want to create a macro to generate such declarations for you. Be aware that this will likely force you to engage with the question of constness.

  • Fix the functions / CHAR16 datatype: it's unclear what other kind of adjustments might be needed, but where you want functions to be compatible with arguments that are wide string literals (of any flavor) then the best way would be to assign types to their parameters that are consistent with that objective. That might mean changing your CHAR16 to wchar_t, or perhaps to char16_t if you want to rely on a compiler supporting that. I would also recommend doing away with the type alias or macro CHAR16, whichever it is, and using the the chose standard type. Also, use const qualification for those function parameters that should accept string literals.