|
trønderen wrote: they were too huge for me to read every word - and in particular: to understand every word of it
Yeah, me too.
The last language spec I read was C11 and C++11 and haven't looked at C++14 or higher. I've learned some the new language features but not reading the specs anymore unless I have to.
|
|
|
|
|
Randor wrote: It reminds me of this old blog post: This processor has no stack Slight sidetrack: Fortran did not allow recursion until the Fortran 90 version; the memory usage was fixed. One of the arguments brought up to keep Fortran alive was that you never risked a stack overflow in Fortran; it was safer than stack languages. Besides: Since there were no stack management, the call overhead was reduced.
At least in its first standard version, Ada required recursive functions to be flagged as such in the program text, and that the maximum calling/recursion depth to be statically determinable at compile time, so that the maximum stack requirement can be calculated in advance.
CHILL also required recursive functions to be marked as such, but I am not sure if you were allowed to code an 'uncontrolled' recursion. At least, to manually search for stack overflow problems, you could limit the search to functions marked as recursive.
|
|
|
|
|
trønderen wrote: Slight sidetrack If you want to get further sidetracked you can watch Aaron Ballman go over all the C23 changes. He's on both the C and C++ committees. Video was posted yesterday.
I'm not going to be using any C23 anytime soon, but I try to keep up with all the language changes.
|
|
|
|
|
He is clear and concise, and he definitely knows what he is talking about. The seventy minutes were certainly not wasted time.
Yet, for at least a third, maybe half of it, my immediate reaction as a C# developer is either, 'Do you still have to struggle with this?' Or, 'Have you really been without this until now?' ... I sort of knew; I was using C a few years back. This video certainly doesn't make me long back to C.
Also, I am (not) surprised to see that features are adopted from C++, after C++ has adopted them from C# (and other languages). It is presented as if C++ was the inventor of several mechanisms that were only slowly adopted from from other languages. I have a strong feeling of NIH.
It is interesting to see what 'they' - those in other camps - are doing. Even if I don't see a single thing that makes me exclaim 'Why don't they do it that way in C# as well?'
|
|
|
|
|
trønderen wrote: the C/C++ scoping/life time rules apply to the source code only and how it 'conceptually' is executed, and does not relate to the compiled code and how it actually runs.
Yep.
So as per the OP
"will allocate memory for an int every frame."
To me that is a question about how the compiler emitted code works and has nothing to do with the spec.
It seems to me to be obviously referring to the stack frame (emitted code.) And for comparison both parts of that are meaningless if the compiler emitted code uses a CPU register.
Compliance for the spec only applies to the 'scope' which means the visibility and the lifetime of the data.
Thus no one can use a compliant compiler and then complain when they attempt to circumvent the scope, for example by using a pointer. Because a compliant compiler can (but is not required) to reuse either a stack frame slot or a CPU register.
It is however possible to use a compliant compiler and deliberately circumvent the scope and it will work. At least for that compiler and that version of the compiler. Might even work in some cases in the same emitted binary and not others depending on how the compiler (not the spec) decides to optimize.
|
|
|
|
|
To add to what jschell said about the spec, I'd point out that modern compilers are surprisingly good at code analysis. I often run into situations where I ask the debugger for a variable value on an optimized build and get a message similar to "variable optimized away". So the compiler only needs to produce code as if a variable exists. If it can deduce the value of the variable for its lifetime, it doesn't need to actually provide storage space for it. There's obviously things you might do that would require there actually be space for it on the stack, like passing its address to a function, for example.
Keep Calm and Carry On
|
|
|
|
|
k5054 wrote: To add to what jschell said about the spec Which part was correct?
|
|
|
|
|
I understand variable scope to work the way (I think?) jschell is describing.
That is, of course the i itself is bounded by {} of the for loop (if I recall correctly) and yet I don't know that the spec is clear where the var has to be allocated/deallocated except perhaps limiting it to the method in question - I'm not even sure there to be honest.
I don't have the head for reams of specifications so I tend to avoid them unless I need to resolve something specific. The above just comes from what I remember of using it, plus a bit of an educated guess - not all systems *could* potentially allocate variables at greater granularity than a method. Some assemblers require you to pre-reserve the entire stack frame you'll be using for that function. In that case, what's a C/C++ compiler to do? The variables effectively live for the entire life of the routine. Sure they're scoped more narrowly than that but the actual physical memory would be there.
That's my understanding and I could be very wrong.
Check out my IoT graphics library here:
https://honeythecodewitch.com/gfx
And my IoT UI/User Experience library here:
https://honeythecodewitch.com/uix
|
|
|
|
|
Calin Negru wrote: the program will allocate memory for an int every frame.
No, it does not. An int is a "value type", and value types are usually allocated on the stack, not the heap. Also, in your example, the anint variable is only allocated (or "pushed" onto the stack once, upon execution of the loop initializer. The value in that stack location is changed on every iteration of the loop. Once the loop is complete, that variable is popped off the stack and will no longer exist.
Of course, all of this is a bit generalized and is not accurate in all cases. It is possible to allocate a value type on the heap, generally called "boxing".
|
|
|
|
|
|
Dave Kreskowiak wrote: It is possible to allocate a value type on the heap, generally called "boxing". Are you referring to managed code or native code with this statement?
The difficult we do right away...
...the impossible takes slightly longer.
|
|
|
|
|
Managed. In my illness induced stupor, I thought we were in the C# forum.
|
|
|
|
|
The typical way for functions to work is allocating whatever stack space they need once in the prologue. But there's no need for anint to be in memory here. Or to exist at all for that matter, since the loop trivially doesn't do anything and can be skipped. MSVC compiles this function like this, anint isn't anywhere, not in memory, not in a register, just gone.
If it was going to be in memory, then only one instance of it needs to exist, so that's what happens. Or should happen anyway. If a compiler individually allocated separate copies of that variable for each iteration, I would file a bug report.
|
|
|
|
|
|
By the way in general it is not right to think of variables as being allocated anywhere, neither in memory nor in registers. Variable are not the "thing" that is allocated, and any given variable may end being in zero or more places at the same time, if you insist on looking at it like that. It's not a completely useless mental model, which is probably why it persists, but that's as a lie-to-children. If a variable is assigned to various times (in the static sense: not so much several times in a loop, but several times in straight line code), those different "versions" of the variable may well end up in different places. SSA considers those different "versions" of the variable to be different variables altogether. Furthermore, even one "version" of a variable can be split into multiple live ranges - that's not just theoretical, there can be multiple good reasons to split it and allocated the pieces to different places. For example, there are often restrictions on which set of register can be used for some instructions, such as on x64 divisions and "legacy" shift-by-variable instructions.
For example, if we consider this code with a division and shift-by-variable:
int test(int x, int y)
{
x = x / y;
return y << x;
}
[MSVC compiles it like this, for x64](https://godbolt.org/z/T8dGWMzhc) (why doesn't this link linkify?)
0 x$ = 8
1 y$ = 16
2 int test(int,int) PROC
3 mov r8d, edx
4 mov eax, ecx
5 cdq
6 idiv r8d
7 mov ecx, eax
8 shl r8d, cl
9 mov eax, r8d
10 ret 0
11 int test(int,int) ENDP
On lines 0 and 1 MSVC helpfully defined stack offsets for x and y, which aren't used, they never end up being on the stack. x is passed in via ecx , and y via edx .
x begins in ecx , then is copied to eax (line 4) because idiv takes the dividend in edx:eax , the division leaves it in eax (only because the code happens to assign the result of x / y back to x - to be clear, the output would be in eax either way, but eax could have represented some other variable otherwise), the original un-divided value of x is still in ecx at this point (after the division on line 6 but before the mov on line 7) but we need the new value to be in ecx , because shl needs the shift count to be in cl which is the lowest byte of ecx . Clearly if we ask "where is x ", it depends on which line of the assembly code (not even the C++ source code) we ask that question about.
y begins in edx , but it cannot stay there because idiv uses edx as input for the upper half of the dividend, and as output for the remainder, so y is copied to r8d , and it stays there. The result of y << x is copied into eax (the return value needs to be in eax ) but that's not really y itself. I could have written y <<= x; return y; and then the same assembly code results, but then eax does represent y .
Let's turn things up a notch. I wrote that a variable may be in multiple places, let's see it:
#include <stddef.h>
int test(size_t N, int *data)
{
int sum = 0;
for (size_t i = 0; i < N; i++)
sum += data[i];
return sum;
}
Compiler Explorer
N$ = 8
data$ = 16
int test(unsigned __int64,int * __ptr64) PROC
xor r8d, r8d
mov r11, rcx
mov r10d, r8d
mov eax, r8d
cmp rcx, 8
jb SHORT $LN9@test
xorps xmm2, xmm2
and rcx, -8
movdqa xmm1, xmm2
npad 3
$LL4@test:
movdqu xmm0, XMMWORD PTR [rdx+rax*4]
paddd xmm0, xmm2
movdqa xmm2, xmm0
movdqu xmm0, XMMWORD PTR [rdx+rax*4+16]
add rax, 8
paddd xmm0, xmm1
movdqa xmm1, xmm0
cmp rax, rcx
jb SHORT $LL4@test
paddd xmm1, xmm2
movdqa xmm0, xmm1
psrldq xmm0, 8
paddd xmm1, xmm0
movdqa xmm0, xmm1
psrldq xmm0, 4
paddd xmm1, xmm0
movd r10d, xmm1
$LN9@test:
mov r9d, r8d
cmp rax, r11
jae SHORT $LN20@test
mov rcx, r11
sub rcx, rax
cmp rcx, 2
jb SHORT $LC14@test
lea rcx, QWORD PTR [r11-1]
npad 1
$LL16@test:
add r8d, DWORD PTR [rdx+rax*4]
add r9d, DWORD PTR [rdx+rax*4+4]
add rax, 2
cmp rax, rcx
jb SHORT $LL16@test
$LC14@test:
cmp rax, r11
jae SHORT $LN15@test
add r10d, DWORD PTR [rdx+rax*4]
$LN15@test:
lea eax, DWORD PTR [r9+r8]
add eax, r10d
ret 0
$LN20@test:
mov eax, r10d
ret 0
int test(unsigned __int64,int * __ptr64) ENDP
Lots of stuff going on here, but here's the important part: there are 4 sums, held in one vector register. xmm2 usually holds those sums. After paddd xmm0, xmm2 it's really xmm0 that holds the sums, then movdqa xmm2, xmm0 immediately copies them back to xmm2 though. And by the way, yes I think that's a mildly silly way to do it, MSVC could have used paddd xmm2, XMMWORD PTR [rdx+rax*4] instead of that movdqu \ paddd \ movdqa sequence, and while "number of instructions" is a poor metric I do believe that that would just be a better way to do it. Especially on CPUs that do not have move-elimination. But whatever, MSVC does what it does.
After the label $LL16@test there is a small unrolled-by-a-factor-of-2 loop where both r8d and r9d are used to calculate more sums, but are they sum ? However you look at it, r8d and r9d are used to calculate part of the sum. r10d also holds part of the sum at this point, namely the part that was calculated by the vectorized loop .. also up to one extra element may be summed into r10d , if one element is left over (ie if N is odd). After $LN15@test , a lea and add are used to add up all 3 parts of the sum that exist at that point.
modified 6-Oct-23 7:05am.
|
|
|
|
|
I'm using the following function for years and used it under any Version of Windows since XP. It creates a systemtask with the start type set to "auto", just like it is expected to do. But under windows 11, the start type of the installed service always defaults to "manual" when being created. Any help solving this is very much appreciated. Am using VS2022 using toolset 1.41_XP (for reasons)
(already added some extra check for win 11... but still the start type defaults to manual in the newly created task)
static int manage_service(int action) {
SC_HANDLE hSCM = NULL, hService = NULL;
SERVICE_DESCRIPTION descr = { server_name };
char path[PATH_MAX + 20];
int success = 1;
GetModuleFileName(NULL, path, sizeof(path));
strncat(path, " ", sizeof(path));
strncat(path, service_magic_argument, sizeof(path));
if (IsRunAsAdministrator()) {
if ((hSCM = OpenSCManager(NULL, NULL, action == ID_INSTALL_SERVICE ?
GENERIC_WRITE : GENERIC_READ)) == NULL) {
success = 0;
show_error();
}
else if (action == ID_INSTALL_SERVICE) {
hService = CreateService(hSCM, service_name, service_name,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
path, NULL, NULL, NULL, NULL, NULL);
if (hService) {
ChangeServiceConfig(hService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr);
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 11;
if (GetVersionEx((OSVERSIONINFO*)&osvi)) {
ChangeServiceConfig(hService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}
}
else {
show_error();
}
}
else if (action == ID_REMOVE_SERVICE) {
if ((hService = OpenService(hSCM, service_name, DELETE)) == NULL ||
!DeleteService(hService)) {
show_error();
}
}
else if ((hService = OpenService(hSCM, service_name,
SERVICE_QUERY_STATUS)) == NULL) {
success = 0;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
else {
if (action == ID_INSTALL_SERVICE) {
RunServiceAsAdmin('I', path, service_name);
}
else if (action == ID_REMOVE_SERVICE) {
RunServiceAsAdmin('R', path, service_name);
}
else {
if ((hSCM = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) {
success = 0;
show_error();
}
if ((hService = OpenService(hSCM, service_name,
SERVICE_QUERY_STATUS)) == NULL) {
success = 0;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
}
return success;
}
|
|
|
|
|
Hmmm,
Your code looks good to me. Although it would be nice if you captured the return values of ChangeServiceConfig .
I would recommend debugging this by checking the Event logs. Look for event ID 7040 in the "Service Control Manager" log source. You might need to enable auditing.
Also, try temporarily adding a Windows Defender exclusion on the service file path if your executable is unsigned/untrusted. I'm wondering if Defender is blocking the change.
|
|
|
|
|
Just found out somethhing more.... As soon as I invoke the service creation function from within the program, I do get the normal service controll manager asking for elevated rights in order to create the service, what is exactly what happen. But then the service gets created with start type set to "manual".
If I do start the program manually "as Administrator" and then invoke the service creation function, the service gets created correctly with start type "auto". So there probably might be a problem with my elevation of rights!?... will check this. Strange though, that it works fin under any Windows version since XP... just not windows 11...
Here is the code to start with elevated rights:
BOOL IsRunAsAdministrator()
{
BOOL isRunAsAdmin = FALSE;
DWORD dwError = ERROR_SUCCESS;
PSID pAdministratorsGroup = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pAdministratorsGroup))
{
goto Cleanup;
}
if (!CheckTokenMembership(NULL, pAdministratorsGroup, &isRunAsAdmin))
{
goto Cleanup;
}
Cleanup:
if (pAdministratorsGroup)
{
FreeSid(pAdministratorsGroup);
pAdministratorsGroup = NULL;
}
return isRunAsAdmin;
}
void RunServiceAsAdmin(char ch, const char *program, const char* name)
{
char param[255];
SHELLEXECUTEINFO sei = { sizeof(sei) };
memset(param, 0 , sizeof(param));
sei.lpVerb = "runas";
sei.lpFile = "sc.exe";
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;
if(ch == 'I')
{
sprintf(param, "create \"%s\" binPath= \"%s\" DisplayName=\"%s\"", name, program, name);
}
else
{
sprintf(param, "delete \"%s\"", name);
}
sei.lpParameters = param;
if (!ShellExecuteEx(&sei))
{
show_error();
}
}
modified 23-Sep-23 18:23pm.
|
|
|
|
|
Well,
You appear to have a function that is checking if you are running as Administrator. Could you show me the content of that function?
|
|
|
|
|
Here is some more complete code:
BOOL IsRunAsAdministrator()
{
BOOL isRunAsAdmin = FALSE;
DWORD dwError = ERROR_SUCCESS;
PSID pAdministratorsGroup = NULL;
SID_IDENTIFIER_AUTHORITY NtAuthority = SECURITY_NT_AUTHORITY;
if (!AllocateAndInitializeSid(
&NtAuthority,
2,
SECURITY_BUILTIN_DOMAIN_RID,
DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0,
&pAdministratorsGroup))
{
goto Cleanup;
}
if (!CheckTokenMembership(NULL, pAdministratorsGroup, &isRunAsAdmin))
{
goto Cleanup;
}
Cleanup:
if (pAdministratorsGroup)
{
FreeSid(pAdministratorsGroup);
pAdministratorsGroup = NULL;
}
return isRunAsAdmin;
}
void RunServiceAsAdmin(char ch, const char *program, const char* name)
{
char param[255];
SHELLEXECUTEINFO sei = { sizeof(sei) };
memset(param, 0 , sizeof(param));
sei.lpVerb = "runas";
sei.lpFile = "sc.exe";
sei.hwnd = NULL;
sei.nShow = SW_NORMAL;
if(ch == 'I')
{
sprintf(param, "create \"%s\" binPath= \"%s\" DisplayName=\"%s\"", name, program, name);
}
else
{
sprintf(param, "delete \"%s\"", name);
}
sei.lpParameters = param;
if (!ShellExecuteEx(&sei))
{
show_error();
}
}
static int manage_service(int action) {
SC_HANDLE hSCM = NULL, hService = NULL;
SERVICE_DESCRIPTION descr = { server_name };
char path[PATH_MAX + 20];
int success = 1;
GetModuleFileName(NULL, path, sizeof(path));
strncat(path, " ", sizeof(path));
strncat(path, service_magic_argument, sizeof(path));
if (IsRunAsAdministrator()) {
if ((hSCM = OpenSCManager(NULL, NULL, action == ID_INSTALL_SERVICE ?
GENERIC_WRITE : GENERIC_READ)) == NULL) {
success = 0;
show_error();
}
else if (action == ID_INSTALL_SERVICE) {
hService = CreateService(hSCM, service_name, service_name,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START, SERVICE_ERROR_NORMAL,
path, NULL, NULL, NULL, NULL, NULL);
if (hService) {
ChangeServiceConfig(hService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &descr);
OSVERSIONINFOEX osvi;
ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);
osvi.dwMajorVersion = 11;
if (GetVersionEx((OSVERSIONINFO*)&osvi)) {
ChangeServiceConfig(hService, SERVICE_NO_CHANGE, SERVICE_AUTO_START,
SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
}
}
else {
show_error();
}
}
else if (action == ID_REMOVE_SERVICE) {
if ((hService = OpenService(hSCM, service_name, DELETE)) == NULL ||
!DeleteService(hService)) {
show_error();
}
}
else if ((hService = OpenService(hSCM, service_name,
SERVICE_QUERY_STATUS)) == NULL) {
success = 0;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
else {
if (action == ID_INSTALL_SERVICE) {
RunServiceAsAdmin('I', path, service_name);
}
else if (action == ID_REMOVE_SERVICE) {
RunServiceAsAdmin('R', path, service_name);
}
else {
if ((hSCM = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) {
success = 0;
show_error();
}
if ((hService = OpenService(hSCM, service_name,
SERVICE_QUERY_STATUS)) == NULL) {
success = 0;
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
}
}
return success;
}
static LONG queryServiceStatus(const char* serviceName){
SC_HANDLE hSCM = NULL, hService = NULL;
SERVICE_STATUS_PROCESS ssStatus;
DWORD dwBytesNeeded;
LONG status = 0;
if ((hSCM = OpenSCManager(NULL, NULL, GENERIC_READ)) == NULL) {
show_error();
return 0;
}
hService = OpenService(
hSCM,
serviceName,
SERVICE_QUERY_STATUS);
if (hService == NULL)
{
goto END_QUERY;
}
if (!QueryServiceStatusEx(
hService,
SC_STATUS_PROCESS_INFO,
(LPBYTE) &ssStatus,
sizeof(SERVICE_STATUS_PROCESS),
&dwBytesNeeded ) )
{
goto END_QUERY;
}
if(ssStatus.dwCurrentState == SERVICE_RUNNING)
{
status = SERVICE_RUNNING;
goto END_QUERY;
}
modified 23-Sep-23 18:31pm.
|
|
|
|
|
I do see a bug.
SHELLEXECUTEINFO sei = { sizeof(sei) };
You should zero that struct out. Then set the cbSize member. Not sure if this is causing your problem though.
I'm on my TV right now so reviewing on my couch. But don't see any other issues.
|
|
|
|
|
Thanks for the tip!
Am also currently looking into a way to use the ControlService utility via CreateProcess to install the service, instead of calling CreateService directly... But not sure if I can pull that off correctly...
|
|
|
|
|
I just noticed in your original post:
Rick R. 2023 wrote:
Am using VS2022 using toolset 1.41_XP
Do you get the same behavior if you compile for Windows 11?
|
|
|
|
|
Starting to suspect the toolset, too.
Have to fix a bunch of linker problems caused by my old and pretty messy project settings, in order to test with a newer version... might take a while.
|
|
|
|
|
Well,
Alot of members check the forums everyday. Sometimes it just helps to have a few other eyes look at the issue.
Rick R. 2023 wrote: Have to fix a bunch of linker problems
XP to Win11 is a big jump, I can imagine.
|
|
|
|
|